sos-4.8.0/0000775000175000017500000000000014660147624010767 5ustar arifarifsos-4.8.0/docs/0000775000175000017500000000000014660147624011717 5ustar arifarifsos-4.8.0/docs/policies.rst0000664000175000017500000000024314660147624014257 0ustar arifarif``sos.policies`` --- Policy Interface ===================================== .. automodule:: sos.policies :members: :undoc-members: :show-inheritance: sos-4.8.0/docs/archive.rst0000664000175000017500000000050514660147624014072 0ustar arifarif``sos.archive`` --- Archive Interface ===================================== .. automodule:: sos.archive :noindex: :members: :undoc-members: :show-inheritance: .. autoclass:: sos.archive.Archive :members: :undoc-members: .. autoclass:: sos.archive.FileCacheArchive :members: :undoc-members: sos-4.8.0/docs/clusters.rst0000664000175000017500000000030514660147624014313 0ustar arifarif``sos.collector.clusters`` --- Cluster Interface ================================================= .. automodule:: sos.collector.clusters :members: :undoc-members: :show-inheritance: sos-4.8.0/docs/utilities.rst0000664000175000017500000000025214660147624014463 0ustar arifarif``sos.utilities`` --- Utilites Interface ======================================== .. automodule:: sos.utilities :members: :undoc-members: :show-inheritance: sos-4.8.0/docs/parsers.rst0000664000175000017500000000027214660147624014131 0ustar arifarif``sos.cleaner.parsers`` --- Parser Interface ============================================= .. automodule:: sos.cleaner.parsers :members: :undoc-members: :show-inheritance: sos-4.8.0/docs/index.rst0000664000175000017500000000466114660147624013567 0ustar arifarifSoS === Sos is an extensible, portable, support data collection tool primarily aimed at Linux distributions and other UNIX-like operating systems. This is the SoS developer documentation, for user documentation refer to: https://github.com/sosreport/sos/wiki This project is hosted at: https://github.com/sosreport/sos For the latest version, to contribute, and for more information, please visit the project pages or join the mailing list. To clone the current main (development) branch run: .. code:: git clone git@github.com:sosreport/sos.git Reporting bugs ^^^^^^^^^^^^^^ Please report bugs via the mailing list or by opening an issue in the GitHub Issue Tracker Mailing list ^^^^^^^^^^^^^ `sos-devel `_ is the mailing list for any sos-related questions and discussion. Patch submissions and reviews are welcome too. Patches and pull requests ^^^^^^^^^^^^^^^^^^^^^^^^^ Patches can be submitted via the mailing list or as GitHub pull requests. If using GitHub please make sure your branch applies to the current main branch as a 'fast forward' merge (i.e. without creating a merge commit). Use the git rebase command to update your branch to the current main branch if necessary. Documentation ============= User and API `documentation `_ is automatically generated using `Sphinx `_ and `Read the Docs `_. Wiki ^^^^ `How to write a plugin `_ `How to write a policy `_ `Plugin options `_ To help get your changes merged quickly with as few revisions as possible please refer to the `Contributor Guidelines `_ when submitting patches or pull requests. Installation ============ Manual Installation ^^^^^^^^^^^^^^^^^^^ .. code:: python3 setup.py install Pre-built Packaging ^^^^^^^^^^^^^^^^^^^ Fedora/RHEL users install via yum: ``yum install sos`` Debian users install via apt: ``apt install sosreport`` Ubuntu (14.04 LTS and above) users install via apt: ``sudo apt install sosreport`` API === Core Reference ^^^^^^^^^^^^^^ .. toctree:: :maxdepth: 4 archive clusters parsers policies plugins reporting utilities sos-4.8.0/docs/plugins.rst0000664000175000017500000000026514660147624014135 0ustar arifarif``sos.report.plugins`` --- Plugin Interface =========================================== .. automodule:: sos.report.plugins :members: :undoc-members: :show-inheritance: sos-4.8.0/docs/reporting.rst0000664000175000017500000000030114660147624014454 0ustar arifarif``sos.report.reporting`` --- Reporting Interface ================================================ .. automodule:: sos.report.reporting :members: :undoc-members: :show-inheritance: sos-4.8.0/docs/conf.py0000664000175000017500000002005514660147624013220 0ustar arifarif#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # SoS documentation build configuration file, created by # sphinx-quickstart on Fri Aug 1 11:43:30 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'SoS' project_copyright = '2014, Bryn Reeves' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '4.8.0' # The full version, including alpha/beta/rc tags. release = '4.8.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'SoSdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'SoS.tex', 'SoS Documentation', 'Bryn Reeves', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'sos', 'SoS Documentation', ['Bryn Reeves'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'SoS', 'SoS Documentation', 'Bryn Reeves', 'SoS', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False sos-4.8.0/bin/0000775000175000017500000000000014660147624011537 5ustar arifarifsos-4.8.0/bin/sos0000775000175000017500000000114414660147624012271 0ustar arifarif#!/usr/bin/python3 # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import sys import os try: sys.path.insert(0, os.getcwd()) from sos import SoS except KeyboardInterrupt: raise SystemExit() if __name__ == '__main__': sos = SoS(sys.argv[1:]) sos.execute() os._exit(0) # vim:ts=4 et sw=4 sos-4.8.0/bin/sos-collector0000775000175000017500000000215114660147624014254 0ustar arifarif#!/usr/bin/python3 # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. """ sos entry point. """ import sys import os import time try: # allow running from the git checkout, even though as of 4.0 we are moving # binaries into a bin/ top-level directory. sys.path.insert(0, os.getcwd()) from sos import SoS except KeyboardInterrupt: raise SystemExit() if __name__ == '__main__': msg = ("WARNING: the 'sos-collector' command has been deprecated in " " favor of the new 'sos' command, E.G. 'sos collect', and will be " "removed in the upcoming sos-4.9 release.\n" "Redirecting to 'sos collect %s'" % (' '.join(sys.argv[1:]) or '')) print(msg) time.sleep(0.5) args = sys.argv[1:] args.insert(0, 'collect') sos = SoS(args) sos.execute() # vim:ts=4 et sw=4 sos-4.8.0/bin/sosreport0000775000175000017500000000214114660147624013523 0ustar arifarif#!/usr/bin/python3 # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. """ sos entry point. """ import sys import os import time try: # allow running from the git checkout, even though as of 4.0 we are moving # binaries into a bin/ top-level directory. sys.path.insert(0, os.getcwd()) from sos import SoS except KeyboardInterrupt: raise SystemExit() if __name__ == '__main__': msg = ("WARNING: the 'sosreport' command has been deprecated in favor " "of the new 'sos' command, E.G. 'sos report', and will be removed " "in the upcoming sos-4.9 release.\n" "Redirecting to 'sos report %s'" % (' '.join(sys.argv[1:]) or '')) print(msg) time.sleep(0.5) args = sys.argv[1:] args.insert(0, 'report') sos = SoS(args) sos.execute() # vim:ts=4 et sw=4 sos-4.8.0/test-requirements.txt0000664000175000017500000000003014660147624015221 0ustar arifarifavocado-framework<104.0 sos-4.8.0/po/0000775000175000017500000000000014660147624011405 5ustar arifarifsos-4.8.0/po/it.po0000664000175000017500000000715214660147624012366 0ustar arifarif# translation of it.po to # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: it\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-11-06 15:02+1000\n" "Last-Translator: \n" "Language-Team: \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versione %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "il plugin %s non é valido e verrà ignorato" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "il plugin %s non é valido e verrà ignorato" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "il plugin %s non si installa, ignorato" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "il plugin %s non si installa, ignorato" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "non é stato trovato nessun plugin valido " #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "I seguenti plugin sono attivi:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Nessun plugin abilitato." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "I seguenti plugin sono disattivati:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Sono disponibili le seguenti opzioni per il plugin:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nessuna opzione disponibile per il plugin." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "non é stato trovato nessun plugin valido " #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Sono disponibili le seguenti opzioni per il plugin:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Premere INVIO per continuare, o CTRL-C per usicre.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Cifratura dell'archivio, in corso ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Plugin in esecuzione. Attendere prego ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Creazione di un archivio compresso, in corso ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "non é stato attivato nessun plugin" sos-4.8.0/po/th.po0000664000175000017500000001044014660147624012357 0ustar arifarif# Thai translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007, 2009. msgid "" msgstr "" "Project-Id-Version: sos.th\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2009-05-13 12:12+0000\n" "Last-Translator: \n" "Language-Team: Thai \n" "Language: th\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Lokalize 0.3\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (รุ่น %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "ส่วนขยาย %s ไม่ถูกต้อง จะข้ามไป" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "ส่วนขยาย %s ไม่ถูกต้อง จะข้ามไป" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "ส่วนขยาย %s ติดตั้งไม่ได้ จะข้ามไป" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "ส่วนขยาย %s ติดตั้งไม่ได้ จะข้ามไป" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ไม่พบส่วนขยายที่ถูกต้อง" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "ส่วนขยายต่อไปนี้เปิดใช้งานอยู่" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ไม่มีส่วนขยายเปิดใช้งาน" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "ส่วนขยายต่อไปนี้ปิดการใช้งานอยู่:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "มีตัวเลือกเหล่านี้จากส่วนขยาย:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "ไม่มีตัวเลือกจากส่วนขยาย" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ไม่พบส่วนขยายที่ถูกต้อง" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "มีตัวเลือกเหล่านี้จากส่วนขยาย:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "กด ENTER เพื่อทำงานต่อ หรือ CTRL+C เพื่อออก\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "กำลังเข้ารหัสไฟล์..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "กำลังสร้างไฟล์บีบอัด..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "ไม่มีส่วนขยายที่ถูกต้องเปิดใช้งาน" sos-4.8.0/po/bn.po0000664000175000017500000000554614660147624012356 0ustar arifarif# Bengali translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/pa.po0000664000175000017500000001074114660147624012350 0ustar arifarif# translation of pa.po to Punjabi # Punjabi translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # Jaswinder Singh , 2007. # Jaswinder Singh , 2011. msgid "" msgstr "" "Project-Id-Version: pa\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-11 15:33+0530\n" "Last-Translator: Jaswinder Singh \n" "Language-Team: PLTG\n" "Language: pa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.6.1\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (ਵਰਜਨ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "ਪਲੱਗਇਨ %s ਪ੍ਰਮਾਣਿਤ ਨਹੀਂ ਹੈ, ਛੱਡ ਰਿਹਾ ਹੈ" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "ਪਲੱਗਇਨ %s ਪ੍ਰਮਾਣਿਤ ਨਹੀਂ ਹੈ, ਛੱਡ ਰਿਹਾ ਹੈ" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "ਪਲੱਗਇਨ %s ਇੰਸਟਾਲ ਨਹੀਂ ਹੋਇਆ, ਛੱਡ ਰਿਹਾ ਹੈ" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "ਪਲੱਗਇਨ %s ਇੰਸਟਾਲ ਨਹੀਂ ਹੋਇਆ, ਛੱਡ ਰਿਹਾ ਹੈ" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ਕੋਈ ਯੋਗ ਪਲੱਗਿਨ ਨਹੀਂ ਲੱਭਿਆ" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "ਹੇਠਲੇ ਪਲੱਗਇਨ ਯੋਗ ਕੀਤੇ ਹਨ:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ਕੋਈ ਪਲੱਗਇਨ ਯੋਗ ਨਹੀਂ ਹੈ।" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "ਹੇਠਲੇ ਪਲੱਗਇਨ ਅਯੋਗ ਹਨ।" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "ਹੇਠਲੀਆਂ ਪਲੱਗਇਨ ਚੋਣਾਂ ਉਪਲੱਬਧ ਹਨ:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "ਕੋਈ ਪਲੱਗਇਨ ਚੋਣ ਉਪਲੱਬਧ ਨਹੀਂ ਹੈ।" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ਕੋਈ ਯੋਗ ਪਲੱਗਿਨ ਨਹੀਂ ਲੱਭਿਆ" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "ਹੇਠਲੀਆਂ ਪਲੱਗਇਨ ਚੋਣਾਂ ਉਪਲੱਬਧ ਹਨ:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ਜਾਰੀ ਰੱਖਣ ਲਈ ENTER ਦਬਾਓ, ਜਾਂ ਬੰਦ ਕਰਨ ਲਈ CTRL-C ਦਬਾਓ।\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "ਆਰਚੀਵ ਇਨਕ੍ਰਿਪਟ ਹੋ ਰਿਹਾ ਹੈ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " ਪਲੱਗਇਨ ਚਲਾ ਰਿਹਾ। ਉਡੀਕ ਕਰੋ ਜੀ ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "ਸੰਕੁਚਿਤ ਆਰਚੀਵ ਬਣਾ ਰਿਹਾ ਹੈ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "ਕੋਈ ਸਹੀ ਪਲੱਗਇਨ ਯੋਗ ਨਹੀਂ ਕੀਤਾ" sos-4.8.0/po/tr.po0000664000175000017500000000747114660147624012403 0ustar arifarif# Turkish translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Hasan Alp iNAN , 2010. # Hasan Alp İNAN , 2010. msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-10-19 21:49+0300\n" "Last-Translator: Hasan Alp İNAN \n" "Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Lokalize 1.0\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (sürüm %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "%s eklentisi doğrulanamadı, atlanıyor" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "%s eklentisi doğrulanamadı, atlanıyor" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "%s eklentisi kurulamıyor, atlanıyor" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "%s eklentisi kurulamıyor, atlanıyor" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "hiç geçerli eklenti bulunamadı" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Aşağıdaki eklentiler şu an etkin:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Hiçbir eklenti etkin değil." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Aşağıdaki eklentiler şu an devre dışı:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Aşağıdaki eklenti seçenekleri kullanılabilir:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Hiç eklenti seçeneği mevcut değil." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "hiç geçerli eklenti bulunamadı" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Aşağıdaki eklenti seçenekleri kullanılabilir:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" "Devam etmek için ENTER'a basınız veya çıkmak için CTRL-C 'ye basınız.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Arşiv şifreleniyor..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Eklentiler çalışıyor. Lütfen bekleyiniz ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Sıkıştırılmış arşiv oluşturuluyor..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "hiç geçerli eklenti etkinleştirilmedi" sos-4.8.0/po/ar.po0000664000175000017500000000761614660147624012361 0ustar arifarif# sos Arabic translation file # Copyright (C) 2007, Red Hat UK, Ltd. # Imed Chihi , 2007. # Abdalrahim Fakhouri , 2010. msgid "" msgstr "" "Project-Id-Version: sos 1.7\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-06-08 23:36+0300\n" "Last-Translator: Abdalrahim Fakhouri \n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (الإصدار %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "الملحق %s غير سليم، تم تعطيله" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "الملحق %s غير سليم، تم تعطيله" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "لا يمكن تثبيت الملحق %s، تم تعطيله." #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "لا يمكن تثبيت الملحق %s، تم تعطيله." #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "لا توجد ملحقات صالحة" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "تم تفعيل الملحقات التالية" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "لم يتم تفعيل أي ملحق." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "تم تعطيل الملحقات الملحقات التالية" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "يمكن تحديد خيارات الملحقات التالية" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "لا توجد خيارات للملحقات" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "لا توجد ملحقات صالحة" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "يمكن تحديد خيارات الملحقات التالية" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "اضعط زر الإدخال للمتابعة، أو زر التحكّم مع C للخروج.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "جاري تشفير الأرشيف ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " جاري تشغيل الإضافات. يرجى الانتظار ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "جاري إنشاء الأرشيف المضغوط ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "لم يتم تفعيل أي ملحقات" sos-4.8.0/po/bg.po0000664000175000017500000000763114660147624012344 0ustar arifarif# Bulgarian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-08-05 15:37+0100\n" "Last-Translator: Ivelin \n" "Language-Team: none\n" "Language: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (версия %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "плъгин %s не се валидира, прескачане" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "плъгин %s не се валидира, прескачане" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "плъгин %s не се инсталира, прескачане" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "плъгин %s не се инсталира, прескачане" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "не са открити валидни плъгин-и." #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Следните плъгин-и са включени:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Няма включен плъгин." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Следните plugin-и са изключени:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Следните plugin опции са налични:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Няма налични plugin опции." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "не са открити валидни плъгин-и." #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Следните plugin опции са налични:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Натиснете ENTER за да продължите или CTRL-C за изход.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Кодиране на архива..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Стартирване на plugin-и. Моля изчакайте ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Създаване на компресиран архив..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "няма включени валидни plugin-и" sos-4.8.0/po/nb.po0000664000175000017500000000634214660147624012351 0ustar arifarif# Norwegian bokmål translations for the sos package. # Copyright (C) 2007 Red Hat, Inc. # Kjartan Maraas , 2010. # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-10-31 19:50+0100\n" "Last-Translator: Kjartan Maraas \n" "Language-Team: Norwegian bokmål \n" "Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versjon %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ingen gyldige tillegg funnet" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Ingen tillegg slått på." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ingen gyldige tillegg funnet" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Trykk LINJESKIFT for å fortsette, eller CTRL-C for å avslutte.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Krypterer arkiv..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Lager komprimert arkiv..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/vi.po0000664000175000017500000000562114660147624012367 0ustar arifarif# Vietnamese translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=1; plural=0;\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/am.po0000664000175000017500000000554614660147624012354 0ustar arifarif# Amharic translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/bs.po0000664000175000017500000000707114660147624012356 0ustar arifarif# Bosnian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: bs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosizjestaj (verzija %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "plugin %s se nije mogao potvrditi, preskace se" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "plugin %s se nije mogao potvrditi, preskace se" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "plugin %s se nije instalirao, preskace se " #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "plugin %s se nije instalirao, preskace se " #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ispravan plugin nije nadjen" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Sljedeci plugin-i su trenutno osposobljeni:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Nema osposobljenih plugin-a." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Sljedeci su plugin-i onesposobljeni:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Sjedeci su plugini-i na raspolaganju:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nema plugin-a na raspolaganju." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ispravan plugin nije nadjen" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Sjedeci su plugini-i na raspolaganju:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Pritisnite ENTER da nastavite, ili CTRL-C da odustanete.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Sifrira se arhiva ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Pokrecu se plugin-i. Molim vas pricekajte ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Kreira se komprimirana arhiva ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "Nema ispravnih plugin-a na raspolaganju." sos-4.8.0/po/he.po0000664000175000017500000000562414660147624012350 0ustar arifarif# Hebrew translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/gu.po0000664000175000017500000001153114660147624012361 0ustar arifarif# translation of sos.trunk.gu.po to Gujarati # Gujarati translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Ankit Patel , 2007. # Sweta Kothari , 2011. msgid "" msgstr "" "Project-Id-Version: sos.trunk.gu\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-04 17:39+0530\n" "Last-Translator: Sweta Kothari \n" "Language-Team: Gujarati\n" "Language: gu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (આવૃત્તિ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "પ્લગઈન %s માન્ય થઈ શક્યું નહિં, અવગણી રહ્યા છીએ" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "પ્લગઈન %s માન્ય થઈ શક્યું નહિં, અવગણી રહ્યા છીએ" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "પ્લગઈન %s સ્થાપિત થતું નથી, અવગણી રહ્યા છીએ" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "પ્લગઈન %s સ્થાપિત થતું નથી, અવગણી રહ્યા છીએ" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "કોઈ માન્ય પ્લગઈનો મળ્યા નહિં" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "નીચેના પ્લગઈનો વર્તમાનમાં સક્રિય કરવામાં આવેલ છે:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "કોઈ પ્લગઈન સક્રિય કરેલ નથી." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "નીચેના પ્લગઈનો વર્તમાનમાં નિષ્ક્રિય કરેલ છે:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "નીચેના પ્લગઈન વિકલ્પો ઉપલબ્ધ છે:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "કોઈ પ્લગઈન વિકલ્પો ઉપલબ્ધ નથી." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "કોઈ માન્ય પ્લગઈનો મળ્યા નહિં" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "નીચેના પ્લગઈન વિકલ્પો ઉપલબ્ધ છે:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ચાલુ રાખવા માટે ENTER દબાવો, અથવા બંધ કરવા માટે CTRL-C દબાવો.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "પેટી એનક્રિપ્ટ કરી રહ્યા છીએ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " પ્લગઇનોને ચલાવી રહ્યા છીએ. મહેરબાની કરીને થોભો ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "સંકુચિત પેટી બનાવી રહ્યા છીએ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "કોઈ માન્ય પ્લગઈનો સક્રિય કરેલ હતા નહિં" sos-4.8.0/po/hi.po0000664000175000017500000001127514660147624012353 0ustar arifarif# translation of sos.trunk.hi.po to Hindi # Hindi translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Rajesh Ranjan , 2007. # Rajesh Ranjan , 2011. msgid "" msgstr "" "Project-Id-Version: sos.trunk.hi\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-17 15:06+0530\n" "Last-Translator: Rajesh Ranjan \n" "Language-Team: Hindi \n" "Language: hi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (संस्करण %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "प्लगिन %s वैध नहीं कर सकता है, छोड़ रहा है" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "प्लगिन %s वैध नहीं कर सकता है, छोड़ रहा है" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "प्लगिन %s अधिष्ठापित नहीं कर रहा है, छोड़ रहा है" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "प्लगिन %s अधिष्ठापित नहीं कर रहा है, छोड़ रहा है" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "कोई वैध प्लगिन नहीं मिला" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "निम्नलिखित प्लगिन अभी सक्रिय है:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "कोई प्लगिन सक्रिय नहीं है." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "निम्न प्लगिन अभी निष्क्रिय है:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "निम्नलिखित प्लगिन विकल्प उपलब्ध है:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "कोई प्लगिन विकल्प उपलब्ध नहीं." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "कोई वैध प्लगिन नहीं मिला" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "निम्नलिखित प्लगिन विकल्प उपलब्ध है:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ENTER को जारी रखने के लिए दबाएं, या CTRL-C छोड़ने के लिए.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "अभिलेख गोपित कर रहा है..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " प्लगिन चला रहा है. कृपया प्रतीक्षा करें ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "संकुचित अभिलेख बना रहा है..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "कोई वैध प्लगिन सक्रिय नहीं था" sos-4.8.0/po/mk.po0000664000175000017500000000555114660147624012362 0ustar arifarif# Macedonian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/af.po0000664000175000017500000000555014660147624012340 0ustar arifarif# Afrikaans translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/ur.po0000664000175000017500000000554314660147624012402 0ustar arifarif# Urdu translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/nso.po0000664000175000017500000000555314660147624012554 0ustar arifarif# Language nso translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/en_GB.po0000664000175000017500000000677214660147624012733 0ustar arifarif# English translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: en_GB\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "plugin %s does not validate, skipping" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "plugin %s does not validate, skipping" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "plugin %s does not install, skipping" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "plugin %s does not install, skipping" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "no valid plugins found" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "The following plugins are currently enabled:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "No plugin enabled." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "The following plugins are currently disabled:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "The following plugin options are available:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "No plugin options available." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "no valid plugins found" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "The following plugin options are available:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Press ENTER to continue, or CTRL-C to quit.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Encrypting archive..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Creating compressed archive..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "no valid plugins were enabled" sos-4.8.0/po/nds.po0000664000175000017500000000652314660147624012537 0ustar arifarif# translation of sos.trunk.po to # Low German translation of sos # This file is distributed under the same license as the sos package. # Copyright (C) 2007 Red hat, Inc. # # # Nils-Christoph Fiedler , 2010. msgid "" msgstr "" "Project-Id-Version: sos.trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-12-05 10:16+0100\n" "Last-Translator: Nils-Christoph Fiedler \n" "Language-Team: Fedora Low German \n" "Language: nds\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Language: Low German\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (Verschoon %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Keen Plugin aktivert." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Keene Pluginoptschoonen verfögbar." #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Entslötel Archiv..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Plugins lööpen. Bidde töven ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Erstelle komprimertes Archiv..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/sv.po0000664000175000017500000000755214660147624012406 0ustar arifarif# Swedish translations for the sos package. # This file is distributed under the same license as the sos package. # Copyright © 2009-2010 Free Software Foundation, Inc. # Göran Uddeborg , 2009-2010. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-08-11 22:07+0200\n" "Last-Translator: Göran Uddeborg \n" "Language-Team: Swedish \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "insticksmodul %s validerar inte, hoppar över" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "insticksmodul %s validerar inte, hoppar över" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "insticksmodul %s installerar inte, hoppar över" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "insticksmodul %s installerar inte, hoppar över" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "inga giltiga insticksmoduler funna" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Följande insticksmoduler är för närvarande aktiva:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Inga insticksmoduler aktiverade." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Följande insticksmoduler är för närvarande inaktiva:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Följande flaggor för insticksmoduler är tillgängliga:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Inga flaggor för insticksmoduler finns tillgängliga." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "inga giltiga insticksmoduler funna" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Följande flaggor för insticksmoduler är tillgängliga:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Tryck RETUR för att fortsätta, eller CTRL-C för att avbryta.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Krypterar arkiv ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Kör insticksmoduler. Var god dröj ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Skapar komprimerat arkiv ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "inga giltiga insticksmoduler var aktiva" sos-4.8.0/po/pt_BR.po0000664000175000017500000000737114660147624012763 0ustar arifarif# Brazilian Portuguese translations for sos package. # Igor Pires Soares , 2007. # Taylon Silmer , 2010. msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-12 15:19-0300\n" "Last-Translator: Taylon Silmer \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versão %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "o plugin %s não validou, ignorando" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "o plugin %s não validou, ignorando" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "o plugin %s não instala, ignorando" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "o plugin %s não instala, ignorando" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "nenhum plugin válido encontrado" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Os seguintes plugins estão habilitados no momento:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Nenhum plugin está habilitado." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Os seguintes plugins estão desabilitados no momento:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "As seguintes opções de plugins estão disponíveis:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nenhuma opção de plugins está disponível." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "nenhum plugin válido encontrado" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "As seguintes opções de plugins estão disponíveis:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Pressione ENTER para continuar ou CTRL-C para encerrar.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Criptografando o pacote..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Executando os plugins. Por favor aguarde..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Criando pacote compactado..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "nenhum plugin válido estava habilitado" sos-4.8.0/po/lt.po0000664000175000017500000000573214660147624012373 0ustar arifarif# Lithuanian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" "%100<10 || n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/sr.po0000664000175000017500000001023114660147624012366 0ustar arifarif# Serbian translations for sos # Copyright (C) 2007 Red Hat, Inc. # This file is distributed under the same license as the sos package. # Miloš Komarčević , 2009. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-28 23:56+0100\n" "Last-Translator: Miloš Komarčević \n" "Language-Team: Serbian \n" "Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (верзија %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "додатак %s се није оверио, прескачем" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "додатак %s се није оверио, прескачем" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "додатак %s се није инсталирао, прескачем" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "додатак %s се није инсталирао, прескачем" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "нису пронађени ваљани додаци" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Следећи додаци су тренутно укључени:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Нема укључених додатака." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Следећи додаци су тренутно искључени:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Доступне су следеће опције додатка:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Нема доступних опција додатка." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "нису пронађени ваљани додаци" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Доступне су следеће опције додатка:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Притисните ENTER за наставак, или CTRL-C за излаз.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Шифрирам архиву..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Покрећем додатке. Молимо сачекајте ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Правим компримовану архиву..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "нису укључени ваљани додаци" sos-4.8.0/po/te.po0000664000175000017500000001230114660147624012352 0ustar arifarif# translation of sos.trunk.te.po to Telugu # Telugu translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Krishna Babu K , 2009. # Krishnababu Krothapalli , 2011. msgid "" msgstr "" "Project-Id-Version: sos.trunk.te\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-07 17:21+0530\n" "Last-Translator: Krishnababu Krothapalli \n" "Language-Team: Telugu \n" "Language: te\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Lokalize 1.1\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (వర్షన్ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "ప్లగ్‌యిన్ %s నిర్ధారించబడలేదు, వదిలివేయుచున్నది" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "ప్లగ్‌యిన్ %s నిర్ధారించబడలేదు, వదిలివేయుచున్నది" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "ప్లగిన్ %s సంస్థాపించబడలేదు, వదిలివేయుచున్నది" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "ప్లగిన్ %s సంస్థాపించబడలేదు, వదిలివేయుచున్నది" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "చెల్లునటువంటి ప్లగిన్సు కనబడలేదు" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "ఈ క్రింది ప్లగిన్సు ప్రస్తుతం చేతనం చేయబడివున్నాయి:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ఏ ప్లగిన్ చేతనం చేయబడిలేదు." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "క్రింది ప్లగిన్సు ప్రస్తుతం అచేతనం చేయబడివున్నాయి:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "ఈ క్రింది ప్లగిన్ ఐచ్చికాలు అందుబాటులో వున్నాయి:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "ఎటువంటి ప్లగిన్ ఐచ్చికాలు అందుబాటులో లేవు." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "చెల్లునటువంటి ప్లగిన్సు కనబడలేదు" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "ఈ క్రింది ప్లగిన్ ఐచ్చికాలు అందుబాటులో వున్నాయి:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "కొనసాగించుటకు దయచేసి ENTER వత్తండి, లేదా నిష్క్రమించుటకు CTRL-C వత్తండి.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "ఆర్చివ్‌ను ఎన్క్రిప్టు చేయుచున్నది..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " ప్లగిన్స్ నడుపుచున్నది. దయచేసి వేచివుండండి ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "కుదించిన ఆర్చీవ్‌ను సృష్టిస్తోంది..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "చెల్లునటువంటి ప్లగిన్సు చేతనము చేయబడిలేవు" sos-4.8.0/po/da.po0000664000175000017500000000750514660147624012340 0ustar arifarif# Danish translations for sos package. # Copyright (C) 2007-10 # Automatically generated, 2007. # Kris Thomsen , 2009, 2010. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-12 13:39+0100\n" "Last-Translator: Kris Thomsen \n" "Language-Team: Danish 1);\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "udvidelsesmodulet %s validerer ikke, springer over" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "udvidelsesmodulet %s validerer ikke, springer over" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "udvidelsesmodulet %s installerer ikke, springer over" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "udvidelsesmodulet %s installerer ikke, springer over" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ingen gyldige udvidelsesmoduler fundet" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Følgende udvidelsesmoduler er aktiveret i øjeblikket:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Ingen udvidelsesmoduler aktiveret." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Følgende udvidelsesmoduler er deaktiveret i øjeblikket:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Følgende indstillinger for udvidelsesmodul er tilgængelige:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Ingen indstillinger tilgængelige for udvidelsesmodul." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ingen gyldige udvidelsesmoduler fundet" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Følgende indstillinger for udvidelsesmodul er tilgængelige:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Tryk ENTER for at fortsætte, eller CTRL-C for at afslutte.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Krypterer arkiv..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Kører udvidelsesmoduler. Vent venligst ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Opretter komprimeret arkiv..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "ingen gyldige udvidelsesmoduler er aktiveret" sos-4.8.0/po/ja.po0000664000175000017500000001021214660147624012333 0ustar arifarif# translation of ja.po to Japanese # Japanese translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Kiyoto Hashida , 2007, 2010. msgid "" msgstr "" "Project-Id-Version: ja\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-05-31 15:20+0900\n" "Last-Translator: Kiyoto Hashida \n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: Plural-Forms: nplurals=2; plural=(n!=1);\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sos レポート (バージョン %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "プラグイン %s は認証できません、スキップします" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "プラグイン %s は認証できません、スキップします" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "プラグイン %s は インストールできません。スキップします" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "プラグイン %s は インストールできません。スキップします" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "有効なプラグインは見付かりません" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "以下のプラグインが現在有効になっています:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "有効なプラグインはありません" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "以下のプラグインは現在無効になっています:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "以下のプラグインオプションが使用できます:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "プラグインオプションは使用できません。" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "有効なプラグインは見付かりません" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "以下のプラグインオプションが使用できます:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ENTER を押して継続するか、又は CTRL-C で終了します。\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "アーカイブを暗号化しています..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " プラグインを実行中です。少しお待ち下さい ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "アーカイブを圧縮しています..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "有効な正当プラグインはありませんでした" sos-4.8.0/po/fa.po0000664000175000017500000000554614660147624012345 0ustar arifarif# Persian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/es.po0000664000175000017500000001041514660147624012355 0ustar arifarif# Fedora Spanish translation of sos.trunk. # This file is distributed under the same license as the sos.trunk package. # # Manuel Ospina , 2007. # Héctor Daniel Cabrera , 2010. # Adolfo Jayme Barrientos , 2017. # msgid "" msgstr "" "Project-Id-Version: sos.trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2017-05-15 15:13-0500\n" "Last-Translator: Adolfo Jayme Barrientos \n" "Language-Team: Español \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Poedit 2.0.1\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versión %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "el complemento %s no se puede validar; se ha omitido" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "no se puede validar" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "el complemento %s necesita privilegios administrativos para ejecutarse; se ha omitido" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "requiere privilegios administrativos" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "excluidos" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "omitidos" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "inactivos" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "opcionales" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "sin especificar" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "el complemento %s no se puede instalar; se ha omitido: %s" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "Se proveyeron perfiles desconocidos o inactivos: %s" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "no se encontró ningún complemento válido" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Los complementos siguientes están activados actualmente:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "No se ha activado ningún complemento." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Los complementos siguientes están desactivados actualmente:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Las opciones del complemento siguientes están disponibles:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "No hay opciones de complemento disponibles." #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "no se encontró ningún perfil válido" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "Los siguientes perfiles están disponibles:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Presione INTRO para continuar o CTRL-C para salir.\n" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr " Preparando el archivador…" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr " Preparando los complementos…" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr " Ejecutando los complementos. Espere un momento…" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Creando el archivador comprimido…" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr " %s al finalizar el archivador" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "Error al desplazar el directorio: %s" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "Error al desplazar el archivador: %s" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "Error al desplazar la suma de comprobación: %s" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "no se ha activado ningún complemento válido" sos-4.8.0/po/pl.po0000664000175000017500000000774114660147624012371 0ustar arifarif# Polish translation for sos. # Copyright © 2007-2010, 2017 the sos authors. # This file is distributed under the same license as the sos package. # Piotr Drąg , 2007-2010, 2017. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2017-02-23 19:29+0100\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (wersja %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "nieprawidłowa wtyczka %s, pomijanie" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "nieprawidłowa" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "wtyczka %s do wykonania wymaga uprawnień roota, pomijanie" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "wymaga roota" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "wykluczona" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "pominięta" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "nieaktywna" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "opcjonalna" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "niepodana" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "nie można zainstalować wtyczki %s, pomijanie: %s" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "Podano nieznane lub nieaktywne profile: %s" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "nie odnaleziono żadnych prawidłowych wtyczek" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Obecnie włączone wtyczki:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Brak włączonych wtyczek." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Obecnie wyłączone wtyczki:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Dostępne opcje wtyczek:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Brak dostępnych opcji wtyczek." #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "nie odnaleziono żadnych prawidłowych profili" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "Dostępne profile:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Naciśnięcie klawisza Enter kontynuuje, a Ctrl-C zakończy.\n" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr " Przygotowywanie archiwum…" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr " Przygotowywanie wtyczek…" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr " Wykonywanie wtyczek. Proszę czekać…" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Tworzenie skompresowanego archiwum…" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr " %s podczas finalizowania archiwum" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "Błąd podczas przenoszenia katalogu: %s" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "Błąd podczas przenoszenia pliku archiwum: %s" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "Błąd podczas przenoszenia pliku sumy kontrolnej: %s" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "nie włączono żadnych prawidłowych wtyczek" sos-4.8.0/po/sr@latin.po0000664000175000017500000000734314660147624013530 0ustar arifarif# Serbian(Latin) translations for sos # Copyright (C) 2007 Red Hat, Inc. # This file is distributed under the same license as the sos package. # Miloš Komarčević , 2009. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-28 23:56+0100\n" "Last-Translator: Miloš Komarčević \n" "Language-Team: Serbian \n" "Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (verzija %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "dodatak %s se nije overio, preskačem" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "dodatak %s se nije overio, preskačem" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "dodatak %s se nije instalirao, preskačem" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "dodatak %s se nije instalirao, preskačem" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "nisu pronađeni valjani dodaci" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Sledeći dodaci su trenutno uključeni:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Nema uključenih dodataka." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Sledeći dodaci su trenutno isključeni:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Dostupne su sledeće opcije dodatka:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nema dostupnih opcija dodatka." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "nisu pronađeni valjani dodaci" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Dostupne su sledeće opcije dodatka:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Pritisnite ENTER za nastavak, ili CTRL-C za izlaz.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Šifriram arhivu..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Pokrećem dodatke. Molimo sačekajte ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Pravim komprimovanu arhivu..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "nisu uključeni valjani dodaci" sos-4.8.0/po/ko.po0000664000175000017500000000774114660147624012367 0ustar arifarif# translation of ko.po to # Korean translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: ko\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-11-07 09:56+1000\n" "Last-Translator: \n" "Language-Team: \n" "Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sos 리포트 (버전 %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "%s 플러그인이 유효하지 않아 생략합니다." #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "%s 플러그인이 유효하지 않아 생략합니다." #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "%s 플러그인이 설치되지 않아 생략합니다." #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "%s 플러그인이 설치되지 않아 생략합니다." #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "사용 가능한 플러그인이 없습니다." #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "현재 다음과 같은 플러그인이 활성화되어 있습니다:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "활성화된 플러그인이 없습니다:" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "현재 다음과 같은 플러그인이 비활성화되어 있습니다:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "다음과 같은 플러그인 옵션을 사용할 수 있습니다:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "사용 가능한 플러그인 옵션이 없습니다:" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "사용 가능한 플러그인이 없습니다." #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "다음과 같은 플러그인 옵션을 사용할 수 있습니다:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "계속하려면 ENTER를 입력하고, 종료하려면 CTRL-C를 입력하십시오.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "아카이브를 암호화하고 있습니다..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "플러그인을 실행중입니다. 잠시만 기다려주십시오..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "압축 아카이브를 생성 중입니다..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "활성화된 사용 가능한 플러그인이 없습니다." sos-4.8.0/po/ku.po0000664000175000017500000000554614660147624012376 0ustar arifarif# Kurdish translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/my.po0000664000175000017500000000554614660147624012404 0ustar arifarif# Burmese translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/en.po0000664000175000017500000000746514660147624012363 0ustar arifarif# English translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-05-27 09:41+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:390 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:619 #, python-format msgid "plugin %s does not validate, skipping" msgstr "plugin %s does not validate, skipping" #: ../sos/sosreport.py:621 #, fuzzy msgid "does not validate" msgstr "plugin %s does not validate, skipping" #: ../sos/sosreport.py:625 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "plugin %s does not install, skipping" #: ../sos/sosreport.py:627 msgid "requires root" msgstr "" #: ../sos/sosreport.py:635 msgid "excluded" msgstr "" #: ../sos/sosreport.py:639 msgid "skipped" msgstr "" #: ../sos/sosreport.py:643 msgid "inactive" msgstr "" #: ../sos/sosreport.py:647 msgid "optional" msgstr "" #: ../sos/sosreport.py:657 msgid "not specified" msgstr "" #: ../sos/sosreport.py:665 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "plugin %s does not install, skipping" #: ../sos/sosreport.py:669 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:762 msgid "no valid plugins found" msgstr "no valid plugins found" #: ../sos/sosreport.py:766 msgid "The following plugins are currently enabled:" msgstr "The following plugins are currently enabled:" #: ../sos/sosreport.py:772 msgid "No plugin enabled." msgstr "No plugin enabled." #: ../sos/sosreport.py:776 msgid "The following plugins are currently disabled:" msgstr "The following plugins are currently disabled:" #: ../sos/sosreport.py:787 msgid "The following plugin options are available:" msgstr "The following plugin options are available:" #: ../sos/sosreport.py:802 msgid "No plugin options available." msgstr "No plugin options available." #: ../sos/sosreport.py:814 #, fuzzy msgid "no valid profiles found" msgstr "no valid plugins found" #: ../sos/sosreport.py:816 #, fuzzy msgid "The following profiles are available:" msgstr "The following plugin options are available:" #: ../sos/sosreport.py:836 #, fuzzy msgid "no valid presets found" msgstr "no valid plugins found" #: ../sos/sosreport.py:838 #, fuzzy msgid "The following presets are available:" msgstr "The following plugin options are available:" #: ../sos/sosreport.py:859 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Press ENTER to continue, or CTRL-C to quit.\n" #: ../sos/sosreport.py:882 #, fuzzy msgid " Setting up archive ..." msgstr "Encrypting archive..." #: ../sos/sosreport.py:927 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:961 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1169 msgid "Creating compressed archive..." msgstr "Creating compressed archive..." #: ../sos/sosreport.py:1177 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1196 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1208 #, python-format msgid "Error writing checksum for file: %s" msgstr "" #: ../sos/sosreport.py:1222 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1240 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1255 msgid "no valid plugins were enabled" msgstr "no valid plugins were enabled" sos-4.8.0/po/sos.pot0000664000175000017500000000566414660147624012750 0ustar arifarif# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/gl.po0000664000175000017500000000554714660147624012362 0ustar arifarif# Galician translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/nl.po0000664000175000017500000000746314660147624012370 0ustar arifarif# translation of sos.trunk-nl.po to Dutch # Copyright (C) YEAR ORGANIZATION # R.E. van der Luit , 2009, 2010. # Geert Warrink , 2010. msgid "" msgstr "" "Project-Id-Version: sos.trunk-nl\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-07-04 18:00+0200\n" "Last-Translator: Geert Warrink \n" "Language-Team: Fedora\n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.6.1\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sos rapport (versie %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "plug-in %s valideerde niet, wordt overgeslagen" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "plug-in %s valideerde niet, wordt overgeslagen" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "plug-in %s laat zich niet installeren, wordt overgeslagen" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "plug-in %s laat zich niet installeren, wordt overgeslagen" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "geen geldige plug-in gevonden" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "De volgende plug-ins zijn momenteel actief:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Geen plug-in aangezet." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "De volgende plug-ins zijn momenteel actief:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "De volgende plug-in opties zijn beschikbaar:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Geen plug-in opties beschikbaar." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "geen geldige plug-in gevonden" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "De volgende plug-in opties zijn beschikbaar:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Druk op ENTER om verder te gaan, of op CTRL-C om te stoppen.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Archief bestand wordt versleuteld..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Plug-ins worden gedraaid. Wachten a.u.b. ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Gecomprimeerd archief bestand wordt gemaakt..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "er waren geen geldige plug-ins aangezet" sos-4.8.0/po/be.po0000664000175000017500000000555114660147624012341 0ustar arifarif# Belarusian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/is.po0000664000175000017500000000555014660147624012365 0ustar arifarif# Icelandic translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/pt.po0000664000175000017500000000741714660147624012401 0ustar arifarif# Portuguese translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2009-09-05 04:14+0100\n" "Last-Translator: Rui Gouveia \n" "Language-Team: PT \n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Language: Portuguese\n" "Generated-By: pygettext.py 1.5\n" "X-Poedit-Country: PORTUGAL\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versão %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "plugin %s não valida. A passar ao seguinte" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "plugin %s não valida. A passar ao seguinte" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "plugin %s não instala. A passar ao seguinte" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "plugin %s não instala. A passar ao seguinte" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "Não foram encontrados plugins válidos" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Os seguintes plugins estão actualmente activos:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Nenhum plugin activo." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Os seguintes plugins estão actualmente desactivados:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "As seguintes opções de plugins estão disponíveis:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nenhumas opções do plugin disponíveis." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "Não foram encontrados plugins válidos" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "As seguintes opções de plugins estão disponíveis:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Carregue em ENTER para continuar, ou CTRL-C para sair.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "A cifrar arquivo..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "A executar plugins. Por favor, aguarde ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "A criar arquivo comprimido..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "Não foram activados plugins válidos" sos-4.8.0/po/de.po0000664000175000017500000000770714660147624012350 0ustar arifarif# translation of sos.trunk.po to # German translation of sos # This file is distributed under the same license as the sos package. # Copyright (C) 2007 Red hat, Inc. # # # Timo Trinks , 2007. # Fabian Affolter , 2009. # sknirT omiT , 2010. msgid "" msgstr "" "Project-Id-Version: sos.trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-31 14:19+1000\n" "Last-Translator: sknirT omiT \n" "Language-Team: \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: KBabel 1.11.4\n" "X-Poedit-Language: German\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "Plugin %s validiert nicht, wird ausgelassen" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "Plugin %s validiert nicht, wird ausgelassen" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "Plugin %s installiert sich nicht, wird ausgelassen" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "Plugin %s installiert sich nicht, wird ausgelassen" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "keine gültigen Plugins gefunden" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Die folgenden Plugins sind derzeit aktiviert:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Kein Plugin aktiviert." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Die folgenden Plugins sind derzeit deaktiviert:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Die folgenden Plugin-Optionen stehen zur Verfügung:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Keine Plugin-Optionen verfügbar." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "keine gültigen Plugins gefunden" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Die folgenden Plugin-Optionen stehen zur Verfügung:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" "Drücken Sie die EINGABETASTE, um fortzufahren, oder STRG-C, um abzubrechen.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Verschlüssele Archiv..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Plugins werden ausgeführt. Bitte warten ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Erstelle komprimiertes Archiv..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "keine gültigen Plugins wurden aktiviert" sos-4.8.0/po/hr.po0000664000175000017500000000574314660147624012367 0ustar arifarif# Croatian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/et.po0000664000175000017500000000562614660147624012366 0ustar arifarif# Estonian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/cs.po0000664000175000017500000000741314660147624012357 0ustar arifarif# Czech translations for sos package. # Copyright (C) 2007 Free Software Foundation. # # Milan Keršláger , 2010. # msgid "" msgstr "" "Project-Id-Version: 1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-08-22 10:38+0100\n" "Last-Translator: Milan Keršláger \n" "Language-Team: Czech >\n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (verze %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "plugin %s není validní, přeskakuji" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "plugin %s není validní, přeskakuji" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "plugin %s nejde nainstalovat, přeskakuji" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "plugin %s nejde nainstalovat, přeskakuji" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "žádné validní pluginy" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Právě jsou povoleny tyto pluginy:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Žádné pluginy nejsou povoleny." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Následující pluginy jsou právě zakázány:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "K dispozici jsou tato nastavení pluginů:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Žádné nastavení pro pluginy." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "žádné validní pluginy" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "K dispozici jsou tato nastavení pluginů:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Pro pokračování stiskněte ENTER, pro přerušení CTRL+c.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Šifrování archivu..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Spouštění pluginů. Čekejte prosím..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Vytváření komprimovaného archivu..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "Žádné validní pluginy nejsou povoleny." sos-4.8.0/po/as.po0000664000175000017500000001153214660147624012352 0ustar arifarif# translation of sos.trunk.as.po to Assamese # Assamese translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Amitakhya Phukan , 2009. msgid "" msgstr "" "Project-Id-Version: sos.trunk.as\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2009-09-08 19:15+0530\n" "Last-Translator: Amitakhya Phukan \n" "Language-Team: Assamese\n" "Language: as\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (সংস্কৰণ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "প্লাগ-ইন %s অনুমোদন কৰা নাযায়, উপেক্ষা কৰা হৈছে" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "প্লাগ-ইন %s অনুমোদন কৰা নাযায়, উপেক্ষা কৰা হৈছে" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "প্লাগ-ইন %s ইনস্টল কৰা নাযায়, উপেক্ষা কৰা হৈছে " #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "প্লাগ-ইন %s ইনস্টল কৰা নাযায়, উপেক্ষা কৰা হৈছে " #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "কোনো বৈধ প্লাগ-ইন পোৱা নাযায়" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "নিম্নলিখিত প্লাগ-ইনসমূহ বৰ্তমানে সক্ৰিয় কৰা হৈছে:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "কোনো প্লাগ-ইন সক্ৰিয় নাই ।" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "নিম্নলিখিত প্লাগ-ইনসমূহ বৰ্তমানে নিষ্ক্ৰিয় অৱস্থাত আছে:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "প্লাগ-ইন সংক্ৰান্ত নিম্নলিখিত বিকল্পসমূহ উপলব্ধ আছে:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "প্লাগ-ইন সংক্ৰান্ত কোনো বিকল্প উপলব্ধ নহয় ।" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "কোনো বৈধ প্লাগ-ইন পোৱা নাযায়" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "প্লাগ-ইন সংক্ৰান্ত নিম্নলিখিত বিকল্পসমূহ উপলব্ধ আছে:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "আগবঢ়াৰ বাবে ENTER টিপক বা প্ৰস্থান কৰিবলৈ CTRL-C টিপক ।\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "আৰ্কাইভ এনক্ৰিপ্ট কৰা হৈছে..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "কমপ্ৰেছ কৰা আৰ্কাইভ নিৰ্মাণ কৰক..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "কোনো বৈধ প্লাগ-ইন সক্ৰিয় কৰা নহয়" sos-4.8.0/po/lv.po0000664000175000017500000000567314660147624012401 0ustar arifarif# Latvian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : " "2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/or.po0000664000175000017500000001161614660147624012372 0ustar arifarif# translation of sos.trunk.or.po to Oriya # Oriya translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Manoj Kumar Giri , 2009, 2011. msgid "" msgstr "" "Project-Id-Version: sos.trunk.or\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-04 18:25+0530\n" "Last-Translator: Manoj Kumar Giri \n" "Language-Team: Oriya \n" "Language: or\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Lokalize 1.1\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (ସସ୍କରଣ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "ପ୍ଲଗଇନ %s କୁ ବୈଧିକୃତ କରେ ନାହିଁ, ଏଡ଼ାଇଦେଉଅଛି" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "ପ୍ଲଗଇନ %s କୁ ବୈଧିକୃତ କରେ ନାହିଁ, ଏଡ଼ାଇଦେଉଅଛି" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "ପ୍ଲଗଇନ %s ସ୍ଥାପନ କରେନାହିଁ, ଏଡ଼ାଇ ଦେଉଛି" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "ପ୍ଲଗଇନ %s ସ୍ଥାପନ କରେନାହିଁ, ଏଡ଼ାଇ ଦେଉଛି" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "କୌଣସି ବୈଧ ପ୍ଲଗଇନ ମିଳୁନାହିଁ" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "ନିମ୍ନଲିଖିତ ପ୍ଲଗଇନଗୁଡ଼ିକ ବର୍ତ୍ତମାନ ସକ୍ରିୟ ହୋଇଛି:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "କୌଣସି ପ୍ଲଗଇନ ସକ୍ରିୟ ହୋଇନାହିଁ।" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "ନିମ୍ନଲିଖିତ ପ୍ଲଗଇନଗୁଡ଼ିକ ବର୍ତ୍ତମାନ ନିଷ୍କ୍ରିୟ ହୋଇଛି:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "ନିମ୍ନଲିଖିତ ପ୍ଲଗଇନ ବିକଳ୍ପଗୁଡ଼ିକ ଉପଲବ୍ଧ ଅଛି:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "କୌଣସି ପ୍ଲଗଇନ ବିକଳ୍ପ ଉପଲବ୍ଧ ନାହିଁ।" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "କୌଣସି ବୈଧ ପ୍ଲଗଇନ ମିଳୁନାହିଁ" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "ନିମ୍ନଲିଖିତ ପ୍ଲଗଇନ ବିକଳ୍ପଗୁଡ଼ିକ ଉପଲବ୍ଧ ଅଛି:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ଅଗ୍ରସର ହେବା ପାଇଁ ENTER ଦବାନ୍ତୁ, ଅଥବା ବିଦାୟ ନେବା ପାଇଁ CTRL-C ଦବାନ୍ତୁ।\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "ଅଭିଲେଖକୁ ସଂଗୁପ୍ତ ରଖୁଅଛି..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " ପ୍ଲଗଇନଗୁଡ଼ିକ ଚାଲୁଅଛି। ଦୟାକରି ଅପେକ୍ଷାକରନ୍ତୁ ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "ସଙ୍କୁଚିତ ଅଭିଲେଖ ନିର୍ମାଣ କରୁଅଛି..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "କୌଣସି ବୈଧ ପ୍ଲଗଇନକୁ ସକ୍ରିୟ କରାହୋଇନାହିଁ" sos-4.8.0/po/ca.po0000664000175000017500000001046614660147624012337 0ustar arifarif# Catalan translation of iok by Softcatalà # Copyright (C) 2009 Free Software Foundation # This file is distributed under the same license as the iok package. # Xavier Conde Rueda , 2009. # # This file is translated according to the glossary and style guide of # Softcatalà. If you plan to modify this file, please read first the page # of the Catalan translation team for the Fedora project at: # http://www.softcatala.org/projectes/fedora/ # and contact the previous translator. # # Aquest fitxer s'ha de traduir d'acord amb el recull de termes i la guia # d'estil de Softcatalà. Si voleu modificar aquest fitxer, llegiu si # us plau la pàgina de catalanització del projecte Fedora a: # http://www.softcatala.org/projectes/fedora/ # i contacteu l'anterior traductor/a. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2009-05-17 08:45\n" "Last-Translator: Xavier Faus i Torà \n" "Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "informe d'ajuda (versió %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "el connector %s no es valida, ometent" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "el connector %s no es valida, ometent" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "el connector %s no s'instal·la, ometent" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "el connector %s no s'instal·la, ometent" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "cap connector vàlid trobat" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Els següents connectors estan actualment activats:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Cap connector activat." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Els següents connectors estan desactivats:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Les següents opcions dels connectors són disponibles:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "No hi ha cap opció disponible dels connectors." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "cap connector vàlid trobat" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Les següents opcions dels connectors són disponibles:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Premeu Entrar per continuar, o Control-C per sortir.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "S'està xifrant l'arxiu..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Executant complements. Espereu ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "S'està creant l'arxiu comprimit..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "cap connector vàlid està activat" sos-4.8.0/po/zu.po0000664000175000017500000000554314660147624012412 0ustar arifarif# Zulu translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/mr.po0000664000175000017500000001125414660147624012366 0ustar arifarif# translation of sos.trunk.po to Marathi # Marathi translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Sandeep Shedmake , 2009, 2011. msgid "" msgstr "" "Project-Id-Version: sos.trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-11 14:15+0530\n" "Last-Translator: Sandeep Shedmake \n" "Language-Team: Marathi \n" "Language: mr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (आवृत्ती %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "प्लगइन %s तपासले गेले नाही, वगळत आहे" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "प्लगइन %s तपासले गेले नाही, वगळत आहे" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "प्लगइन %s चे प्रतिष्ठापान अशक्य, वगळत आहे" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "प्लगइन %s चे प्रतिष्ठापान अशक्य, वगळत आहे" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "वैध प्लगइनस् आढळले नाही" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "खालील प्लगइनस् सध्या कार्यक्षम केले आहेत:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "प्लगइन कार्यक्षम केले नाही." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "खालील प्लगइनस् सध्या अकार्यक्षम केले आहे:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "खालील प्लगइन पर्याय उपलब्ध आहे:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "प्लगइन पर्याय उपलब्ध नाही." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "वैध प्लगइनस् आढळले नाही" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "खालील प्लगइन पर्याय उपलब्ध आहे:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "पुढे जाण्यासाठी ENTER दाबा, किंवा बाहेर पडण्यासाठी CTRL-C दाबा.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "आर्काइव्ह एनक्रिप्ट करत आहे..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " प्लगईन्स् चालवत आहे. कृपया थांबा ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "आकुंचीत आर्काइव्ह निर्माण करत आहे..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "वैध प्लगइनस् कार्यक्षम केले नाही" sos-4.8.0/po/ta.po0000664000175000017500000001253714660147624012361 0ustar arifarif# translation of ta.po to # Tamil translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # I Felix , 2011. msgid "" msgstr "" "Project-Id-Version: ta\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-02-02 15:23+0530\n" "Last-Translator: I Felix \n" "Language-Team: Tamil \n" "Language: ta\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Lokalize 1.1\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "கூடுதல் இணைப்பு %s தவறாக உள்ளது, எனவே தவிர்க்கிறது" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "கூடுதல் இணைப்பு %s தவறாக உள்ளது, எனவே தவிர்க்கிறது" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "கூடுதல் இணைப்பு %s நிறுவப்படவில்லை, தவிர்க்கப்படுகிறது" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "கூடுதல் இணைப்பு %s நிறுவப்படவில்லை, தவிர்க்கப்படுகிறது" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "சரியான கூடுதல் இணைப்புகள் இல்லை" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "பின்வரும் கூடுதல் இணைப்புகள் தற்போது செயல்படுத்தப்படுகிறது:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "கூடுதல் இணைப்புகள் எதுவும் செயல்படுத்தப்படவில்லை." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "பின்வரும் கூடுதல் இணைப்புகள் தற்போது செயல்நீக்கப்பட்டுள்ளது:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "பின்வரும் கூடுதல் இணைப்பு விருப்பங்கள் உள்ளன:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "கூடுதல் இணைப்பு விருப்பங்கள் எதுவும் இல்லை." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "சரியான கூடுதல் இணைப்புகள் இல்லை" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "பின்வரும் கூடுதல் இணைப்பு விருப்பங்கள் உள்ளன:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ENTER ஐ அழுத்தவும் அல்லது CTRL-C ஐ அழுத்தி வெளியேறவும்.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "காப்பினை மறைகுறியாக்குகிறது..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "" " கூடுதல் இணைப்புகள் இயங்குகிறது. காத்திருக்கவும் ... கூடுதல் இணைப்புகள் இயங்குகிறது. " "பகாத்தஇஉ" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "குறுக்கப்பட்ட காப்பினை உருவாக்குகிறது..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "சரியான கூடுதல் இணைப்புகள் எதுவும் செயல்படுத்தப்படவில்லை" sos-4.8.0/po/id.po0000664000175000017500000000647614660147624012356 0ustar arifarif# Indonesian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "laporan sos membutuhkan hak akses root untuk berjalan." #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "Tidak ada plugin yang valid ditemukan" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Opsi plugin ini memungkinkan:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Tidak ada plugin yang memungkinkan." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Opsi plugin ini sedang tidak mungkin:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Opsi plugin ini tersedia:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "Tidak ada plugin yang valid ditemukan" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Opsi plugin ini tersedia:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Tekan ENTER untuk melanjutkan, atau CTRL+C untuk keluar.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Mengekripsi arsip..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Membuat arsip yang dikompresi..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "Tidak ada plugin yang valid yang dimungkinkan." sos-4.8.0/po/sk.po0000664000175000017500000000742014660147624012365 0ustar arifarif# Slovak translations for sos package. # Copyright (C) 2007 ORGANIZATION # # Ondrej Šulek , 2009, 2010. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-04-15 17:38+0200\n" "Last-Translator: Ondrej Šulek \n" "Language-Team: Slovak \n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Lokalize 1.0\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (verzia %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "nie je možné overiť modul %s, preskakujem" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "nie je možné overiť modul %s, preskakujem" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "nie je možné nainštalovať modul %s, preskakujem" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "nie je možné nainštalovať modul %s, preskakujem" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "nenájdené žiadne platné moduly" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Nasledujúce moduly sú aktuálne povolené:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Žiadny modul nie je povolený." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Nasledujúce moduly sú aktuálne zakázané:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Nasledujúce možnosti modulov sú dostupné:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nie sú dostupné žiadne možnosti modulov." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "nenájdené žiadne platné moduly" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Nasledujúce možnosti modulov sú dostupné:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Stlačte ENTER na pokračovanie, alebo CTRL-C na ukončenie.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Šifrovanie archívu..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Moduly bežia. Prosím čakajte ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Vytváranie komprimovaného archívu..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "neboli povolené žiadne platné moduly" sos-4.8.0/po/nn.po0000664000175000017500000000563714660147624012373 0ustar arifarif# Norwegian Nynorsk translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/bn_IN.po0000664000175000017500000001210414660147624012730 0ustar arifarif# translation of sos.trunk.po to Bengali INDIA # Bengali translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Runa Bhattacharjee , 2007, 2010. msgid "" msgstr "" "Project-Id-Version: sos.trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-08-05 13:46+0530\n" "Last-Translator: Runa Bhattacharjee \n" "Language-Team: Bengali INDIA \n" "Language: bn_IN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (সংস্করণ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "প্লাগ-ইন %s অনুমোদন করা যায়নি, উপেক্ষা করা হচ্ছে" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "প্লাগ-ইন %s অনুমোদন করা যায়নি, উপেক্ষা করা হচ্ছে" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "প্লাগ-ইন %s ইনস্টল করা যায়নি, উপেক্ষা করা হচ্ছে " #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "প্লাগ-ইন %s ইনস্টল করা যায়নি, উপেক্ষা করা হচ্ছে " #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "কোনো বৈধ প্লাগ-ইন পাওয়া যায়নি" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "নিম্নলিখিত প্লাগ-ইনগুলি বর্তমানে সক্রিয় করা হয়েছে:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "কোনো প্লাগ-ইন সক্রিয় নেই।" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "নিম্নলিখিত প্লাগ-ইনগুলি বর্তমানে নিষ্ক্রিয় অবস্থায় রয়েছে:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "প্লাগ-ইন সংক্রান্ত নিম্নলিখিত বিকল্পগুলি উপলব্ধ রয়েছে:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "প্লাগ-ইন সংক্রান্ত কোনো বিকল্প উপলব্ধ নয়।" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "কোনো বৈধ প্লাগ-ইন পাওয়া যায়নি" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "প্লাগ-ইন সংক্রান্ত নিম্নলিখিত বিকল্পগুলি উপলব্ধ রয়েছে:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "এগিয়ে চলার জন্য ENTER টিপুন অথবা প্রস্থান করতে CTRL-C টিপুন।\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "আর্কাইভ এনক্রিপ্ট করা হচ্ছে..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " প্লাগ-ইন সঞ্চালিত হচ্ছে। অনুগ্রহ করে অপেক্ষা করুন ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "কমপ্রেস করা আর্কাইভ নির্মাণ করুন..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "কোনো বৈধ প্লাগ-ইন সক্রিয় করা হয়নি" sos-4.8.0/po/sq.po0000664000175000017500000000554714660147624012403 0ustar arifarif# Albanian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/ml.po0000664000175000017500000001243314660147624012360 0ustar arifarif# translation of sos.trunk.ml.po to # Malayalam translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Ani Peter , 2007. msgid "" msgstr "" "Project-Id-Version: sos.trunk.ml\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-05 21:23+0530\n" "Last-Translator: \n" "Language-Team: \n" "Language: ml\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (%s ലക്കം)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "%s എന്നത് ശരിയായ പ്ളഗ്ഗിന്‍ അല്ല, വേണ്ടെന്ന് വയ്ക്കുന്നു" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "%s എന്നത് ശരിയായ പ്ളഗ്ഗിന്‍ അല്ല, വേണ്ടെന്ന് വയ്ക്കുന്നു" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "%s എന്ന പ്ളഗ്ഗിന്‍ ഇന്‍സ്റ്റോള്‍ ചെയ്യുവാന്‍ സാധ്യമല്ല, ഉപേക്ഷിക്കുന്നു" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "%s എന്ന പ്ളഗ്ഗിന്‍ ഇന്‍സ്റ്റോള്‍ ചെയ്യുവാന്‍ സാധ്യമല്ല, ഉപേക്ഷിക്കുന്നു" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ശരിയായ പ്ളഗ്ഗിനുകള്‍ ലഭ്യമല്ല" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "താഴെ പറഞ്ഞിരിക്കുന്ന പ്ളഗ്ഗിനുകള്‍ നിലവില്‍ സജീവമാണ്:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ഒരു പ്ളഗ്ഗിനും സജ്ജമല്ല." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "താഴെ പറഞ്ഞിരിക്കുന്ന പ്ളഗ്ഗിനുകള്‍ നിലവില്‍ നിറ്‍ജ്ജീവമാണ്:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "താഴെ പറഞ്ഞിരിക്കുന്ന പ്ളഗ്ഗിന്‍ ഉപാധികള്‍ ലഭ്യമാണ്:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "പ്ളഗ്ഗിന്‍ ഉപാധികള്‍ ലഭ്യമല്ല." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ശരിയായ പ്ളഗ്ഗിനുകള്‍ ലഭ്യമല്ല" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "താഴെ പറഞ്ഞിരിക്കുന്ന പ്ളഗ്ഗിന്‍ ഉപാധികള്‍ ലഭ്യമാണ്:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "മുമ്പോട്ട് തുടരുന്നതിനായി ENTER അല്ലെങ്കില്‍ പുറത്ത് കടക്കുന്നതിനായി CTRL-C അമറ്‍ത്തുക.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "ആറ്‍ക്കൈവ് എന്‍ക്രിപ്റ്റ് ചെയ്യുന്നു..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " പ്ലഗിനുകള്‍ പ്രവര്‍ത്തിയ്ക്കുന്നു. ദയവായി കാത്തിരിയ്ക്കുക ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "കംപ്രസ്സ്ഡ് ആറ്‍ക്കൈവ് ഉണ്ടാക്കുന്നു..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "ശരിയായ പ്ളഗ്ഗിനുകള്‍ സജ്ജമല്ല" sos-4.8.0/po/el.po0000664000175000017500000001105414660147624012346 0ustar arifarif# Greek translation for sos.trunk package. # Copyright (C) 2010 Fedora Project Greek Translation Team # Giannis Konstantinidis , 2010. # msgid "" msgstr "" "Project-Id-Version: trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-10 16:21-0500\n" "Last-Translator: Giannis Konstantinidis \n" "Language-Team: Greek \n" "Language: el\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (έκδοση %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "το πρόσθετο %s δεν είναι έγκυρο,η διαδικασία προσπερνάται." #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "το πρόσθετο %s δεν είναι έγκυρο,η διαδικασία προσπερνάται." #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "το πρόσθετο %s δεν μπορεί να εγκατασταθεί,η διαδικασία προσπερνάται" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "το πρόσθετο %s δεν μπορεί να εγκατασταθεί,η διαδικασία προσπερνάται" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "δεν βρέθηκαν έγκυρα πρόσθετα" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Τα παρακάτω πρόσθετα είναι αυτη τη στιγμή ενεργοποιημένα:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Δεν ενεργοποιήθηκε κάποιο πρόσθετο." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Τα παρακάτω πρόσθετα είναι αυτη τη στιγμή απενεργοποιημένα:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Οι παρακάτω ρυθμίσεις για τα πρόσθετα είναι διαθέσιμες:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Δεν υπάρχουν διαθέσιμες ρυθμίσεις για τα πρόσθετα." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "δεν βρέθηκαν έγκυρα πρόσθετα" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Οι παρακάτω ρυθμίσεις για τα πρόσθετα είναι διαθέσιμες:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Πατήστε ENTER για να συνεχίσετε ή CTRL-C για έξοδο.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Το αρχείο κρυπτογραφείται..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Εκτέλεση πρόσθετων. Παρακαλώ περιμένετε ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Δημιουργία συμπιεσμένου αρχείου..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "δεν ενεργοποιήθηκε κάποιο έγκυρο πρόσθετο" sos-4.8.0/po/sl.po0000664000175000017500000000571614660147624012374 0ustar arifarif# Slovenian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" "%100==4 ? 2 : 3);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/de_CH.po0000664000175000017500000000747314660147624012722 0ustar arifarif# German translation of sos # This file is distributed under the same license as the sos package. # Copyright (C) 2007 Red hat, Inc. # # Fabian Affolter , 2009. # msgid "" msgstr "" "Project-Id-Version: SOS\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2009-04-18 01:15+0100\n" "Last-Translator: Fabian Affolter \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: KBabel 1.11.4\n" "X-Poedit-Language: Swiss German\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "Plugin %s validiert nicht, wird ausgelassen" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "Plugin %s validiert nicht, wird ausgelassen" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "Plugin %s installiert sich nicht, wird ausgelassen" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "Plugin %s installiert sich nicht, wird ausgelassen" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "keine gültigen Plugins gefunden" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Die folgenden Plugins sind derzeit aktiviert:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Kein Plugin aktiviert." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Die folgenden Plugins sind derzeit deaktiviert:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Die folgenden Plugin-Optionen stehen zur Verfügung:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Keine Plugin-Optionen verfügbar." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "keine gültigen Plugins gefunden" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Die folgenden Plugin-Optionen stehen zur Verfügung:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" "Drücken Sie die EINGABETASTE, um fortzufahren, oder Ctrl-C, um abzubrechen.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Verschlüssele Archiv ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Erstelle komprimiertes Archiv ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "keine gültigen Plugins wurden aktiviert" sos-4.8.0/po/ro.po0000664000175000017500000000554714660147624012400 0ustar arifarif# Romanian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/ka.po0000664000175000017500000001231614660147624012343 0ustar arifarif# Georgian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2022-07-16 18:22+0200\n" "Last-Translator: Temuri Doghonadze \n" "Language-Team: Georgian <(nothing)>\n" "Language: ka\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Poedit 3.1.1\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (ვერსია %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "დამატება %s არასწორი და გამოტოვებულია" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "არასწორია" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "დამატებას %s გასაშვებად root-ის წვდომები ესაჭიროება. გამოტოვებულია" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "საჭიროებს root მომხმარებელს" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "გამორიცხულია" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "გამოტოვებული" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "არააქტიური" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "არასავალდებულო" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "მითითებული არაა" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "დამატება %s დაყენებული არაა და გამოტოვებულია: %s" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "მითითებულია უცნობი ან არააქტიური პროფილი: %s" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "არცერთი დამატება სწორი არაა" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "ჩართული დამატებები:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ჩართული დამატებების გარეშე." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "ამჟამად გამორთული დამატებები:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "დამატებების ხელმისაწვდომი პარამეტრები:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "დამატებების პარამეტრების გარეშე." #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "სწორი პროფილები ნაპოვნი არაა" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "ხელმისაწვდომია პროფილები:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ENTER გასაგრძელებლად, CTRL-C შესაწყვეტად.\n" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr " არქივის მორგება ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr " დამატებების მორგება ..." #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr " მიმდინარეობს დამატებების გაშვება. მოითმინეთ ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "შეკუმშული არქივის შექმნა ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr " %s არქივის დასრულებისას" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "საქაღალდის გადატანის შეცდომა: %s" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "არქივის ფაილის გადატანის შეცდომა: %s" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "საკონტროლო ჯამის ფაილის გადატანის შეცდომა: %s" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "სწორი ჩართული დამატებების გარეშე" sos-4.8.0/po/hu.po0000664000175000017500000000710714660147624012366 0ustar arifarif# Hungarian translations for sos package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: sos trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-16 17:23+0100\n" "Last-Translator: Peter Bojtos \n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport·(%s változat)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "%s dugasz érvénytelen, kihagyás" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "%s dugasz érvénytelen, kihagyás" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "%s dugasz nem települ, kihagyás" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "%s dugasz nem települ, kihagyás" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "nincs érvényes dugasz" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "A következő dugaszokat engedélyezték:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Nincs engedélyezett dugasz." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "A következő dugaszokat tiltották le:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "A következő dugasz opciók érhetők el:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nincs elérhető dugasz opció." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "nincs érvényes dugasz" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "A következő dugasz opciók érhetők el:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Nyomjon ENTER-t a folytatáshoz, vagy CTRL-C-t a kilépéshez.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Archívum titkosítása…" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Plugin-ek futtatása, kérem várjon…" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Tömörített archívum teremtése…" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "nem engedélyeztek érvényes dugaszt" sos-4.8.0/po/ms.po0000664000175000017500000000554414660147624012374 0ustar arifarif# Malay translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/ru.po0000664000175000017500000001015114660147624012371 0ustar arifarif# translation of ru.po to # Russian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Yulia , 2010. msgid "" msgstr "" "Project-Id-Version: ru\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-30 08:47\n" "Last-Translator: Yulia \n" "Language-Team: Russian\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (версия %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "модуль %s не прошёл проверку. Пропускается." #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "модуль %s не прошёл проверку. Пропускается." #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "модуль %s не устанавливается. Пропускается." #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "модуль %s не устанавливается. Пропускается." #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "верные модули не найдены" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "В настоящее время включены модули:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Нет активных модулей." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "В настоящее время отключены модули:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Доступные опции модулей:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Нет опций." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "верные модули не найдены" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Доступные опции модулей:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Нажмите ENTER для продолжения или CTRL-C для выхода.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Выполняется шифрование архива..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Выполняются модули. Пожалуйста, подождите..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Создаётся архив..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "не включены верные модули" sos-4.8.0/po/zh_TW.po0000664000175000017500000000733714660147624013012 0ustar arifarif# Traditional Chinese Messages for sos. # Copyright (C) 2010 The sos Project (msgids) # This file is distributed under the same license as the sos package. # Chester Cheng , 2007. # Wei-Lun Chao , 2010. # msgid "" msgstr "" "Project-Id-Version: sos 2.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-09-19 00:10+1000\n" "Last-Translator: Wei-Lun Chao \n" "Language-Team: Chinese (traditional) \n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport(版本 %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "外掛程式 %s 無法驗證,因此跳過" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "外掛程式 %s 無法驗證,因此跳過" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "未安裝外掛程式 %s,故而跳過" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "未安裝外掛程式 %s,故而跳過" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "找不到正確的外掛程式" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "目前以下外掛程式已經啟用:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "並未啟用任何外掛程式。" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "目前以下外掛程式已經停用:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "外掛程式有以下選項可用:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "沒有可用的外掛程式選項。" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "找不到正確的外掛程式" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "外掛程式有以下選項可用:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "請按下「Enter」繼續,或按下「CTRL-C」離開。\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "加密壓縮檔…" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " 正在執行外掛程式,請稍待…" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "建立壓縮檔…" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "並未啟用合用的外掛程式" sos-4.8.0/po/hy.po0000664000175000017500000000554714660147624012400 0ustar arifarif# Armenian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/cy.po0000664000175000017500000000554414660147624012370 0ustar arifarif# Welsh translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/lo.po0000664000175000017500000000554614660147624012371 0ustar arifarif# Laotian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/uk.po0000664000175000017500000001033714660147624012370 0ustar arifarif# Ukrainian translations for sos package. # Copyright (C) Free Software Foundation # This file is distributed under the same license as the system-config-display package. # Maxim Dzіumanenko , 2009 # msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-04-11 08:45\n" "Last-Translator: Maxim Dzіumanenko \n" "Language-Team: \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (версія %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "модуль %s не пройшов перевірку автентичності. Пропускається." #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "модуль %s не пройшов перевірку автентичності. Пропускається." #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "модуль %s не встановлюється. Пропускається." #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "модуль %s не встановлюється. Пропускається." #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "не знайдено модулі" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Наразі увімкнені модулі:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Немає активних модулів." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Наразі вимкнені модулі:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Доступні наступні параметри модулів:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Немає параметрів." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "не знайдено модулі" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Доступні наступні параметри модулів:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Натисніть ENTER для продовження або CTRL-C для виходу.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Виконується кодування архіву..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Виконуються модулі. Зачекайте, будь ласка..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Створюється архів..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "відповідні модулі не увімкнені" sos-4.8.0/po/kn.po0000664000175000017500000001217414660147624012362 0ustar arifarif# translation of sos.trunk.kn.po to Kannada # Kannada translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Shankar Prasad , 2009, 2011. msgid "" msgstr "" "Project-Id-Version: sos.trunk.kn\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2011-01-05 11:56+0530\n" "Last-Translator: Shankar Prasad \n" "Language-Team: Kannada \n" "Language: kn\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Lokalize 1.1\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (ಆವೃತ್ತಿ %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "ಪ್ಲಗ್‌ಇನ್ %s ಮಾನ್ಯಗೊಂಡಿಲ್ಲ, ಉಪೇಕ್ಷಿಸಲಾಗುತ್ತಿದೆ" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "ಪ್ಲಗ್‌ಇನ್ %s ಮಾನ್ಯಗೊಂಡಿಲ್ಲ, ಉಪೇಕ್ಷಿಸಲಾಗುತ್ತಿದೆ" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "ಪ್ಲಗ್‌ಇನ್ %s ಅನುಸ್ಥಾಪನೆಗೊಂಡಿಲ್ಲ, ಉಪೇಕ್ಷಿಸಲಾಗುತ್ತಿದೆ" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "ಪ್ಲಗ್‌ಇನ್ %s ಅನುಸ್ಥಾಪನೆಗೊಂಡಿಲ್ಲ, ಉಪೇಕ್ಷಿಸಲಾಗುತ್ತಿದೆ" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "ಮಾನ್ಯವಾದ ಯಾವುದೆ ಪ್ಲಗ್‌ಇನ್‌ಗಳಿಲ್ಲ" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "ಈ ಕೆಳಗಿನ ಪ್ಲಗ್‌ಇನ್‌ಗಳನ್ನು ಶಕ್ತಗೊಳಿಸಲಾಗಿದೆ:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ಯಾವುದೆ ಪ್ಲಗ್‌ಇನ್ ಅನ್ನು ಶಕ್ತಗೊಳಿಸಲಾಗಿಲ್ಲ." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "ಈ ಕೆಳಗಿನ ಪ್ಲಗ್‌ಇನ್‌ಗಳನ್ನು ಅಶಕ್ತಗೊಳಿಸಲಾಗಿದೆ:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "ಈ ಕೆಳಗಿನ ಪ್ಲಗ್‌ಇನ್‌ ಆಯ್ಕೆಗಳು ಲಭ್ಯವಿವೆ:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "ಯಾವುದೆ ಪ್ಲಗ್‌ಇನ್‌ ಆಯ್ಕೆಗಳು ಲಭ್ಯವಿಲ್ಲ." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "ಮಾನ್ಯವಾದ ಯಾವುದೆ ಪ್ಲಗ್‌ಇನ್‌ಗಳಿಲ್ಲ" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "ಈ ಕೆಳಗಿನ ಪ್ಲಗ್‌ಇನ್‌ ಆಯ್ಕೆಗಳು ಲಭ್ಯವಿವೆ:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "ಮುಂದುವರೆಯಲು ENTER ಅನ್ನು ಒತ್ತಿ, ಅಥವ ನಿರ್ಗಮಿಸಲು CTRL-C ಅನ್ನು ಒತ್ತಿ.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "ಆರ್ಕೈವ್ ಅನ್ನು ಗೂಢಲಿಪೀಕರಿಸಲಾಗುತ್ತಿದೆ..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " ಪ್ಲಗ್‌ಇನ್‌ಗಳನ್ನು ಚಲಾಯಿಸಲಾಗುತ್ತಿದೆ. ದಯವಿಟ್ಟು ಕಾಯಿರಿ ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "ಸಂಕುಚನಗೊಳಿಸಲಾದ ಆರ್ಕೈವನ್ನು ನಿರ್ಮಿಸಲಾಗುತ್ತಿದೆ..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "ಮಾನ್ಯವಾದ ಯಾವುದೆ ಪ್ಲಗ್‌ಇನ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ" sos-4.8.0/po/si.po0000664000175000017500000001100714660147624012357 0ustar arifarif# translation of si.po to Sinhala # Sinhalese translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Danishka Navin , 2007. msgid "" msgstr "" "Project-Id-Version: si\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-11-07 19:03+0530\n" "Last-Translator: Danishka Navin \n" "Language-Team: Sinhala \n" "Language: si\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (%s වෙළුම)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "%s ප්ලගීනය සත්‍යාපනය වන්නේ නැත, මගහරිමින්" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "%s ප්ලගීනය සත්‍යාපනය වන්නේ නැත, මගහරිමින්" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "%s ප්ලගීනය ස්ථාපනය වන්නේ නැත, මගහරි" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "%s ප්ලගීනය ස්ථාපනය වන්නේ නැත, මගහරි" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "නිරවද්‍ය පල්ගීන හමුවුයේ නැත" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "පහත දැක්වෙන ප්ලගීන දැනට සක්‍රීයව ඇත:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "ප්ලගීන කිසිවක් සක්‍රීය නැත." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "පහත පල්ගීන විකල්ප දැනට අක්‍රීයව ඇත:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "පහත පල්ගීන විකල්ප භාවිතයට ඇත:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "කිසිදු පල්ගීන විකල්පයක් භාවිතයට නැත." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "නිරවද්‍ය පල්ගීන හමුවුයේ නැත" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "පහත පල්ගීන විකල්ප භාවිතයට ඇත:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "දිගටම කරගෙන යාමට ENTER හෝ පිට වීමට CTRL-C ඔබන්න.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "සංරක්‍ෂණ සංකේතාංකනය කරමින්..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "හැකිලු සංරක්‍ෂක නිර්මාණය කරමින්..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "කිසිදු නිරවද්‍ය පල්ගිනයක් සක්‍රිය කර නැත" sos-4.8.0/po/fi.po0000664000175000017500000000737514660147624012357 0ustar arifarif# Finnish translations for sos package. # Ville-Pekka Vainio , 2009, 2010. msgid "" msgstr "" "Project-Id-Version: sos\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-28 01:03+0200\n" "Last-Translator: Ville-Pekka Vainio \n" "Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versio %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "liitännäinen %s on virheellinen, ohitetaan" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "liitännäinen %s on virheellinen, ohitetaan" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "liitännäinen %s ei asennu, ohitetaan" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "liitännäinen %s ei asennu, ohitetaan" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "kelvollisia liitännäisiä ei löytynyt" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Seuraavat liitännäiset ovat tällä hetkellä käytössä:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Yhtään liitännäistä ei ole käytössä." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Seuraavat liitännäiset ovat tällä hetkellä poissa käytöstä:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Seuraavat liitännäisen asetukset ovat käytettävissä:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Liitännäisellä ei ole asetuksia." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "kelvollisia liitännäisiä ei löytynyt" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Seuraavat liitännäisen asetukset ovat käytettävissä:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Paina ENTER jatkaaksesi, CTRL-C lopettaaksesi.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Salataan arkistoa..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " Suoritetaan liitännäisiä. Odota..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Luodaan pakattua arkistoa..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "yhtään kelvollista liitännäistä ei ole otettu käyttöön" sos-4.8.0/po/eu.po0000664000175000017500000000554514660147624012367 0ustar arifarif# Basque translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/ilo.po0000664000175000017500000000555314660147624012540 0ustar arifarif# Language ilo translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # Automatically generated, 2007. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2007-10-24 08:45\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "" #: ../sos/sosreport.py:979 msgid "does not validate" msgstr "" #: ../sos/sosreport.py:983 #, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, python-format msgid "plugin %s does not install, skipping: %s" msgstr "" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "" #: ../sos/sosreport.py:1172 msgid "no valid profiles found" msgstr "" #: ../sos/sosreport.py:1174 msgid "The following profiles are available:" msgstr "" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "" #: ../sos/sosreport.py:1216 msgid " Setting up archive ..." msgstr "" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "" sos-4.8.0/po/zh_CN.po0000664000175000017500000000706214660147624012753 0ustar arifarif# translation of sos.trunk.po to Wei Liu # Chinese translations for PACKAGE package # PACKAGE �ļ���ķ�. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Leah Liu , 2007, 2010. msgid "" msgstr "" "Project-Id-Version: sos.trunk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-05-28 12:38+1000\n" "Last-Translator: Leah Liu \n" "Language-Team: Wei Liu\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "插件 %s 无效,跳过" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "插件 %s 无效,跳过" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "未安装插件 %s,跳过" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "未安装插件 %s,跳过" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "未发现可用插件" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "目前已启用了以下插件:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "没有启用插件。" #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "目前禁用了以下插件:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "以下插件选项可用:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "没有可用的插件选项。" #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "未发现可用插件" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "以下插件选项可用:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "按 ENTER 键继续,或者 CTRL-C 组合键退出。\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "为归档加密......" #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr " 正在运行插件,请等候 ..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "生成压缩归档......" #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "没有启用任何可用插件" sos-4.8.0/po/fr.po0000664000175000017500000000754414660147624012366 0ustar arifarif# translation of fr.po to French # French translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Decroux Fabien , 2007. # Thomas Canniot , 2010. msgid "" msgstr "" "Project-Id-Version: fr\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2010-03-20 21:28+0100\n" "Last-Translator: Thomas Canniot \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "Generated-By: pygettext.py 1.5\n" "X-Generator: Lokalize 1.0\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (version %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "le plugin %s n'a pas été validé, ignoré" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "le plugin %s n'a pas été validé, ignoré" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "le plugin %s ne s'installe pas, ignoré" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "le plugin %s ne s'installe pas, ignoré" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "aucun plugin valide n'a été trouvé" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Les plugins suivants sont activés :" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Aucun plugin n'est activé." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Les plugins suivants sont désactivés :" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Les options du plugin suivant sont disponibles :" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Aucune option n'est disponible pour ce plugin." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "aucun plugin valide n'a été trouvé" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Les options du plugin suivant sont disponibles :" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Appuyez sur Entrée pour continuer ou CTRL-C pour quitter.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Cryptage de l'archive..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 #, fuzzy msgid " Running plugins. Please wait ..." msgstr "Lancement des extensions. Veuillez patienter..." #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Création d'une archive compressée..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "aucun plugin valide n'a été activé" sos-4.8.0/po/ast.po0000664000175000017500000000733714660147624012546 0ustar arifarif# translation of ast.po to Asturian # Asturian translations for PACKAGE package. # Copyright (C) 2007 ORGANIZATION # # Automatically generated, 2007. # Astur , 2007. msgid "" msgstr "" "Project-Id-Version: ast\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-23 19:17+0100\n" "PO-Revision-Date: 2009-10-24 20:58+0100\n" "Last-Translator: Xandru Martino Ruz \n" "Language-Team: Asturian \n" "Language: ast\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: KBabel 1.11.4\n" #: ../sos/sosreport.py:745 #, python-format msgid "sosreport (version %s)" msgstr "sosreport (versión %s)" #: ../sos/sosreport.py:977 #, python-format msgid "plugin %s does not validate, skipping" msgstr "nun se validó'l plugin %s, inorándolu" #: ../sos/sosreport.py:979 #, fuzzy msgid "does not validate" msgstr "nun se validó'l plugin %s, inorándolu" #: ../sos/sosreport.py:983 #, fuzzy, python-format msgid "plugin %s requires root permissions to execute, skipping" msgstr "nun s'instaló'l plugin %s, inorándolu" #: ../sos/sosreport.py:985 msgid "requires root" msgstr "" #: ../sos/sosreport.py:993 msgid "excluded" msgstr "" #: ../sos/sosreport.py:997 msgid "skipped" msgstr "" #: ../sos/sosreport.py:1001 msgid "inactive" msgstr "" #: ../sos/sosreport.py:1005 msgid "optional" msgstr "" #: ../sos/sosreport.py:1015 msgid "not specified" msgstr "" #: ../sos/sosreport.py:1023 #, fuzzy, python-format msgid "plugin %s does not install, skipping: %s" msgstr "nun s'instaló'l plugin %s, inorándolu" #: ../sos/sosreport.py:1027 #, python-format msgid "Unknown or inactive profile(s) provided: %s" msgstr "" #: ../sos/sosreport.py:1120 msgid "no valid plugins found" msgstr "nun s'atopó un plugin válidu" #: ../sos/sosreport.py:1124 msgid "The following plugins are currently enabled:" msgstr "Los siguientes plugins tán activaos anguaño:" #: ../sos/sosreport.py:1130 msgid "No plugin enabled." msgstr "Dengún plugin ta activáu." #: ../sos/sosreport.py:1134 msgid "The following plugins are currently disabled:" msgstr "Los siguientes plugins nun tán activaos:" #: ../sos/sosreport.py:1145 msgid "The following plugin options are available:" msgstr "Les siguientes opciones del plugin tán disponibles:" #: ../sos/sosreport.py:1160 msgid "No plugin options available." msgstr "Nun hai opciones de plugin disponibles." #: ../sos/sosreport.py:1172 #, fuzzy msgid "no valid profiles found" msgstr "nun s'atopó un plugin válidu" #: ../sos/sosreport.py:1174 #, fuzzy msgid "The following profiles are available:" msgstr "Les siguientes opciones del plugin tán disponibles:" #: ../sos/sosreport.py:1197 msgid "Press ENTER to continue, or CTRL-C to quit.\n" msgstr "Calca INTRO pa siguir o CTRL-C pa colar.\n" #: ../sos/sosreport.py:1216 #, fuzzy msgid " Setting up archive ..." msgstr "Encriptando'l ficheru..." #: ../sos/sosreport.py:1250 msgid " Setting up plugins ..." msgstr "" #: ../sos/sosreport.py:1282 msgid " Running plugins. Please wait ..." msgstr "" #: ../sos/sosreport.py:1490 msgid "Creating compressed archive..." msgstr "Criando un ficheru comprimíu..." #: ../sos/sosreport.py:1498 #, python-format msgid " %s while finalizing archive" msgstr "" #: ../sos/sosreport.py:1517 #, python-format msgid "Error moving directory: %s" msgstr "" #: ../sos/sosreport.py:1540 #, python-format msgid "Error moving archive file: %s" msgstr "" #: ../sos/sosreport.py:1558 #, python-format msgid "Error moving checksum file: %s" msgstr "" #: ../sos/sosreport.py:1574 msgid "no valid plugins were enabled" msgstr "nun s'activó dengún plugin válidu" sos-4.8.0/plugins_overview.py0000664000175000017500000001211214660147624014745 0ustar arifarif# this script generates for each plugin: # - its name # - URL to upstream code # - list of distros # - list of profiles # - list of packages that enable the plugin (no other enabling pieces) # - list of paths it collects (add_copy_spec) # - list of paths it forbits to collect (add_forbidden_path) # - list of commands it calls (add_cmd_output) # # Output of the script: # - a JSON object with plugins in keys # - or CSV format in case "csv" cmdline is provided # # TODO: # - improve parsing that will be never ideal :) # - add other methods: # - add_blockdev_cmd # - add_string_as_file # - ?? import os import re import json import sys PLUGDIR = 'sos/report/plugins' plugs_data = {} # the map of all plugins data to collect # method to parse an item of a_s_c/a_c_o/.. methods # we work on an assumption the item is a string quoted by \" or optionally # by \'. If we detect at least 2 such chars in the item, take what is between # those. def add_valid_item(dest, item): for qoutemark in "\"\'": split = item.split(qoutemark) if len(split) > 2: dest.append(split[1]) return # method to find all items of given method (a_c_s/a_c_o/..) in plugin content, # split by comma; add each valid item to the `dest` list def add_all_items(method, dest, plugfd, wrapopen=r'\(', wrapclose=r'\)'): regexp = f"{method}{wrapopen}(.*?){wrapclose}" for match in re.findall(regexp, plugfd, flags=re.MULTILINE | re.DOTALL): # tuple of distros ended by either (class|from|import) if isinstance(match, tuple): for item in list(match): if item not in ['class', 'from', 'import']: for it in item.split(','): # dirty hack to remove spaces and "Plugin" if "Plugin" not in it: continue it = it.strip(' ()')[0:-6] if len(it): dest.append(it) # list of specs separated by comma .. elif match.startswith('[') or match.startswith('('): for item in match.split(','): add_valid_item(dest, item) # .. or a singleton spec else: add_valid_item(dest, match) # main body: traverse report's plugins directory and for each plugin, grep for # add_copy_spec / add_forbidden_path / add_cmd_output there for plugfile in sorted(os.listdir(PLUGDIR)): # ignore non-py files and __init__.py if not plugfile.endswith('.py') or plugfile == '__init__.py': continue plugname = plugfile[:-3] # if plugname != 'bcache': # continue plugs_data[plugname] = { 'sourcecode': 'https://github.com/sosreport/sos/blob/' f'main/sos/report/plugins/{plugname}.py', 'distros': [], 'profiles': [], 'packages': [], 'copyspecs': [], 'forbidden': [], 'commands': [], 'service_status': [], 'journals': [], 'env': [], } with open(os.path.join(PLUGDIR, plugfile), encoding='utf-8').read().replace('\n', '') as pfd: add_all_items( "from sos.report.plugins import ", plugs_data[plugname]['distros'], pfd, wrapopen='', wrapclose='(class|from|import)' ) add_all_items("profiles = ", plugs_data[plugname]['profiles'], pfd, wrapopen='') add_all_items("packages = ", plugs_data[plugname]['packages'], pfd, wrapopen='') add_all_items("add_copy_spec", plugs_data[plugname]['copyspecs'], pfd) add_all_items("add_forbidden_path", plugs_data[plugname]['forbidden'], pfd) add_all_items("add_cmd_output", plugs_data[plugname]['commands'], pfd) add_all_items("collect_cmd_output", plugs_data[plugname]['commands'], pfd) add_all_items("add_service_status", plugs_data[plugname]['service_status'], pfd) add_all_items("add_journal", plugs_data[plugname]['journals'], pfd) add_all_items("add_env_var", plugs_data[plugname]['env'], pfd) # print output; if "csv" is cmdline argument, print in CSV format, else JSON if (len(sys.argv) > 1) and (sys.argv[1] == "csv"): print("plugin;url;distros;profiles;packages;copyspecs;forbidden;commands;" "service_status;journals;env_vars") for plugname, plugin in plugs_data.items(): # determine max number of lines - usually # "max(len(copyspec),len(commands))" # ignore 'sourcecode' key as it maxline = 1 plugkeys = list(plugin.keys()) plugkeys.remove('sourcecode') for key in plugkeys: maxline = max(maxline, len(plugin[key])) for line in range(maxline): out = ";" if line > 0 else f"{plugname};{plugin['sourcecode']}" for key in plugkeys: out += ";" if line < len(plugin[key]): out += plugin[key][line] print(out) else: print(json.dumps(plugs_data)) sos-4.8.0/sos/0000775000175000017500000000000014660147624011573 5ustar arifarifsos-4.8.0/sos/missing.py0000664000175000017500000000437614660147624013630 0ustar arifarif import sys from sos.component import SoSComponent class MissingCollect(SoSComponent): """This is used as a placeholder for when the local sos installation attempts to import sos.collector, but that module is not present. In those scenarios, it means that sos has been split into sub-packages. Barring incorrect splitting, this 'sos.missing' module should always be available to the main sos package. """ load_policy = False configure_logging = False desc = '(unavailable) Collect an sos report from multiple nodes' missing_msg = ( 'It appears likely that your distribution separately ships a package ' 'called sos collect. Please install it to enable this function' ) def execute(self): sys.stderr.write( "The collect command is unavailable as it appears to be packaged " "separately for your distribution.\n\nPlease install the latest " "sos collect package to enable this functionality.\n" ) def load_options(self): """Override the normal component method to basically ignore all options given, so that we always print the error message that collect is unavailable, rather than failing on the parser when collect is called with options. """ return [] @classmethod def add_parser_options(cls, parser): """Set the --help output for collect to a message that shows that the functionality is unavailable """ msg = ("WARNING: `collect` is not available with this installation! " f"{cls.missing_msg}") parser.epilog = msg return parser class MissingPexpect(MissingCollect): """This is used as a placeholder for when the collect component is locally installed, but cannot be used due to a missing pexpect dependency. """ missing_msg = ( 'Please install the python3-pexpect package for your distribution in ' 'order to enable this function' ) def execute(self): sys.stderr.write( "The collect command is unavailable due to a missing dependency " "on python3-pexpect.\n\nPlease install python3-pexpect to enable " "this functionality.\n" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/__init__.py0000664000175000017500000002004114660147624013701 0ustar arifarif# Copyright 2010 Red Hat, Inc. # Author: Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. """ This module houses the i18n setup and message function. The default is to use gettext to internationalize messages. """ __version__ = "4.8.0" import os import sys import gettext from argparse import ArgumentParser from sos.options import SosListOption 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 class SoS(): """Main entrypoint for sos from the command line Upon intialization, this class loads the basic option parser which will include the options shared by support components/subcommands. This is also where all subcommands present in the local installation are discovered, loaded, and if a matching one is found, intialized. """ def __init__(self, args): self.cmdline = args # define the local subcommands that exist on the system # first import the necessary module, then add an entry to the dict that # follows the tuple format (class, [aliases]), where aliases is a list # of shorthand names to accept in place of the full subcommand # if no aliases are desired, pass an empty list import sos.report import sos.cleaner import sos.help self._components = { 'report': (sos.report.SoSReport, ['rep']), 'clean': (sos.cleaner.SoSCleaner, ['cleaner', 'mask']), 'help': (sos.help.SoSHelper, []) } # some distros do not want pexpect as a default dep, so try to load # collector here, and if it fails add an entry that implies it is at # least present on this installation try: import sos.collector self._components['collect'] = (sos.collector.SoSCollector, ['collector']) except ModuleNotFoundError as err: import sos.missing if 'sos.collector' in str(err.msg): # is not locally installed - packaged separately self._components['collect'] = (sos.missing.MissingCollect, []) elif 'pexpect' in str(err.msg): # cannot be imported due to missing the pexpect dep self._components['collect'] = (sos.missing.MissingPexpect, []) else: # we failed elsewhere, re-raise the exception raise # build the top-level parser _com_string = '' for com, value in self._components.items(): aliases = value[1] aliases.insert(0, com) _com = ', '.join(aliases) desc = value[0].desc _com_string += (f"\t{_com:<30}{desc}\n") usage_string = ("%(prog)s [options]\n\n" "Available components:\n") usage_string = usage_string + _com_string epilog = "See `sos --help` for more information" self.parser = ArgumentParser(usage=usage_string, epilog=epilog) self.parser.register('action', 'extend', SosListOption) # set the component subparsers self.subparsers = self.parser.add_subparsers( dest='component', metavar='component', help='sos component to run' ) self.subparsers.required = True # now build the parser for each component. # this needs to be done here, as otherwise --help will be unavailable # for the component subparsers for comp, value in self._components.items(): _com_subparser = self.subparsers.add_parser( comp, aliases=value[1], prog=f"sos {comp}" ) _com_subparser.usage = f"sos {comp} [options]" _com_subparser.register('action', 'extend', SosListOption) self._add_common_options(_com_subparser) value[0].add_parser_options(parser=_com_subparser) _com_subparser.set_defaults(component=comp) self.args = self.parser.parse_args(self.cmdline) self._init_component() def _add_common_options(self, parser): """Adds the options shared across components to the parser """ global_grp = parser.add_argument_group('Global Options') global_grp.add_argument("--batch", default=False, action="store_true", help="Do not prompt interactively") global_grp.add_argument("--config-file", type=str, action="store", dest="config_file", default="/etc/sos/sos.conf", help="specify alternate configuration file") global_grp.add_argument("--debug", action="store_true", dest="debug", help="enable interactive debugging using the " "python debugger") global_grp.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="only print fatal errors") global_grp.add_argument("-s", "--sysroot", action="store", dest="sysroot", default=None, help="system rootdir path (default='/')") global_grp.add_argument("--tmp-dir", action="store", dest="tmp_dir", default=None, help="specify alternate temporary directory") global_grp.add_argument("-t", "--threads", action="store", dest="threads", default=4, type=int, help="Number of threads to use") global_grp.add_argument("-v", "--verbose", action="count", dest="verbosity", default=0, help="increase verbosity") global_grp.add_argument('-z', '--compression-type', dest="compression_type", choices=['auto', 'gzip', 'xz'], help="compression technology to use") # Group to make tarball encryption (via GPG/password) exclusive encrypt_grp = global_grp.add_mutually_exclusive_group() encrypt_grp.add_argument("--encrypt", default=False, action="store_true", help=("Encrypt the archive, either prompting " "for a password/key or referencing " "an environment variable")) encrypt_grp.add_argument("--encrypt-key", help="Encrypt the archive using a GPG " "key-pair") encrypt_grp.add_argument("--encrypt-pass", help="Encrypt the archive using a password") def _init_component(self): """Determine which component has been requested by the user, and then initialize that component. """ _com = self.args.component if _com not in self._components.keys(): print(f"Unknown subcommand '{_com}' specified") try: _to_load = self._components[_com][0] if _to_load.root_required and not os.getuid() == 0: raise Exception("Component must be run with root privileges") self._component = _to_load(self.parser, self.args, self.cmdline) except Exception as err: print(f"Could not initialize '{_com}': {err}") if self.args.debug: raise err sys.exit(1) def execute(self): self._component.execute() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/0000775000175000017500000000000014660147624013561 5ustar arifarifsos-4.8.0/sos/collector/clusters/0000775000175000017500000000000014660147624015425 5ustar arifarifsos-4.8.0/sos/collector/clusters/pacemaker.py0000664000175000017500000000747614660147624017745 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from xml.etree import ElementTree from sos.collector.clusters import Cluster from sos.utilities import sos_parse_version class pacemaker(Cluster): cluster_name = 'Pacemaker High Availability Cluster Manager' sos_plugins = ['pacemaker'] packages = ('pacemaker',) strict_node_list = True option_list = [ ('online', True, 'Collect nodes listed as online'), ('offline', True, 'Collect nodes listed as offline'), ('only-corosync', False, 'Only use corosync.conf to enumerate nodes') ] def get_nodes(self): self.nodes = [] # try crm_mon first try: if not self.get_option('only-corosync'): try: self.get_nodes_from_crm() except Exception as err: self.log_warn("Falling back to sourcing corosync.conf. " f"Could not parse crm_mon output: {err}") if not self.nodes: # fallback to corosync.conf, in case the node we're inspecting # is offline from the cluster self.get_nodes_from_corosync() except Exception as err: self.log_error(f"Could not determine nodes from cluster: {err}") _shorts = [n for n in self.nodes if '.' not in n] if _shorts: self.log_warn( f"WARNING: Node addresses '{','.join(_shorts)}' may not " "resolve locally if you are not running on a node in the " "cluster. Try using option '-c pacemaker.only-corosync' if " "these connections fail." ) return self.nodes def get_nodes_from_crm(self): """ Try to parse crm_mon output for node list and status. """ xmlopt = '--output-as=xml' # older pacemaker had a different option for xml output _ver = self.exec_primary_cmd('crm_mon --version') if _ver['status'] == 0: cver = _ver['output'].split()[1].split('-')[0] if sos_parse_version(cver) <= sos_parse_version('2.0.3'): xmlopt = '--as-xml' else: return _out = self.exec_primary_cmd( f"crm_mon --one-shot --inactive {xmlopt}", need_root=True ) if _out['status'] == 0: self.parse_crm_xml(_out['output']) def parse_crm_xml(self, xmlstring): """ Parse the xml output string provided by crm_mon """ _xml = ElementTree.fromstring(xmlstring) nodes = _xml.find('nodes') for node in nodes: _node = node.attrib if self.get_option('online') and _node['online'] == 'true': self.nodes.append(_node['name']) elif self.get_option('offline') and _node['online'] == 'false': self.nodes.append(_node['name']) def get_nodes_from_corosync(self): """ As a fallback measure, read corosync.conf to get the node list. Note that this prevents us from separating online nodes from offline nodes. """ self.log_warn("WARNING: unable to distinguish online nodes from " "offline nodes when sourcing from corosync.conf") cc = self.primary.read_file('/etc/corosync/corosync.conf') nodes = re.findall(r'((\sring0_addr:)(.*))', cc) for node in nodes: self.nodes.append(node[-1].strip()) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/__init__.py0000664000175000017500000003734114660147624017546 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import logging from threading import Lock from sos.options import ClusterOption from sos.utilities import bold class Cluster(): """This is the class that cluster profiles should subclass in order to add support for different clustering technologies and environments to sos collect. A profile should at minimum define a package that indicates the node is configured for the type of cluster the profile is intended to serve and then additionally be able to return a list of enumerated nodes via the ``get_nodes()`` method :param commons: The commons dict containing system information. The same as what is handed to ``Plugin()`` :type commons: ``dict`` :cvar option_list: Options supported by the profile, and set by the --cluster-option cmdline arg :vartype option_list: ``list`` of ``tuples`` :cvar packages: What package(s) should this profile enable on :vartype packages: ``tuple`` :cvar sos_plugins: Which plugins to forcibly enable for node reports :vartype sos_plugins: ``list`` :cvar sos_options: Options to pass to report on every node :vartype sos_options: ``dict`` :cvar sos_plugin_options: Plugin options to forcibly set for nodes :vartype sos_plugin_options: ``dict`` :cvar sos_preset: A SoSReport preset to forcibly enable on nodes :vartype sos_preset: ``str`` :cvar cluster_name: The name of the cluster type :vartype cluster_name: ``str`` """ option_list = [] packages = ('',) sos_plugins = [] sos_options = {} sos_plugin_options = {} sos_preset = '' cluster_name = None # set this to True if the local host running collect should *not* be # forcibly added to the node list. This can be helpful in situations where # the host's fqdn and the name the cluster uses are different strict_node_list = False def __init__(self, commons): self.primary = None self.cluster_ssh_key = None self.tmpdir = commons['tmpdir'] self.opts = commons['cmdlineopts'] self.cluster_type = [self.__class__.__name__] for cls in self.__class__.__bases__: if cls.__name__ != 'Cluster': self.cluster_type.append(cls.__name__) self.node_list = None self.lock = Lock() self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') 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() @classmethod def display_help(cls, section): # pylint: disable=too-many-branches if cls is Cluster: cls.display_self_help(section) return section.set_title(f"{cls.cluster_name} Cluster Profile Detailed Help") if cls.__doc__ and cls.__doc__ is not Cluster.__doc__: section.add_text(cls.__doc__) # [1] here is the actual cluster profile elif cls.__mro__[1].__doc__ and cls.__mro__[1] is not Cluster: section.add_text(cls.__mro__[1].__doc__) else: section.add_text( "\n\tDetailed help not available for this profile\n" ) if cls.packages: section.add_text( "Enabled by the following packages: " f"{', '.join(p for p in cls.packages)}", newline=False ) if cls.sos_preset: section.add_text( f"Uses the following sos preset: {cls.sos_preset}", newline=False ) if cls.sos_options: _opts = ', '.join(f'--{k} {v}' for k, v in cls.sos_options.items()) section.add_text(f"Sets the following sos options: {_opts}") if cls.sos_plugins: section.add_text( "Enables the following plugins: " f"{', '.join(plug for plug in cls.sos_plugins)}", newline=False ) if cls.sos_plugin_options: _opts = cls.sos_plugin_options opts = ', '.join(f"{k}={v}" for k, v in _opts.items()) section.add_text( f"Sets the following plugin options: {opts}", newline=False ) if cls.option_list: optsec = section.add_section("Available cluster options") optsec.add_text( "These options may be toggled or changed using " f"'{bold(f'-c {cls.__name__}.$option=$value')}'" ) optsec.add_text( bold( f"\n{' ':<4}{'Option Name':<20}{'Default':<30}" f"{'Description':<20}\n"), newline=False ) for opt in cls.option_list: val = opt[1] if isinstance(val, bool): if val: val = 'True/On' else: val = 'False/Off' _ln = f"{' ':<4}{opt[0]:<20}{val:<30}{opt[2]:<20}" optsec.add_text(_ln, newline=False) @classmethod def display_self_help(cls, section): section.set_title('SoS Collect Cluster Profiles Detailed Help') section.add_text( '\nCluster profiles are used to represent different clustering ' 'technologies or platforms. Profiles define how cluster nodes are ' 'discovered, and optionally filtered, for default executions of ' 'collector.' ) section.add_text( 'Cluster profiles are enabled similarly to SoS report plugins; ' 'usually by package, command, or configuration file presence. ' 'Clusters may also define default transports for SoS collect.' ) from sos.collector import SoSCollector import inspect clusters = SoSCollector._load_modules(inspect.getmodule(cls), 'clusters') section.add_text( 'The following cluster profiles are locally available:\n' ) section.add_text( f"{' ':>8}{'Name':<40}{'Description':<30}", newline=False ) for cluster in clusters: _sec = bold(f"collect.clusters.{cluster[0]}") section.add_text( f"{' ':>8}{_sec:<40}{cluster[1].cluster_name:<30}", newline=False ) def _get_options(self): """Loads the options defined by a cluster and sets the default value""" for opt in self.option_list: option = ClusterOption(name=opt[0], opt_type=opt[1].__class__, value=opt[1], cluster=self.cluster_type, description=opt[2]) self.options.append(option) def _fmt_msg(self, msg): return f'[{self.cluster_type[0]}] {msg}' def log_info(self, msg): """Used to print info messages""" self.soslog.info(self._fmt_msg(msg)) def log_error(self, msg): """Used to print error messages""" self.soslog.error(msg) def log_debug(self, msg): """Used to print debug messages""" self.soslog.debug(self._fmt_msg(msg)) def log_warn(self, msg): """Used to print warning messages""" self.soslog.warning(self._fmt_msg(msg)) def get_option(self, option): """ This is used to by clusters to check if a cluster option was supplied to sos collect :param option: The name of the option to fetch :type option: ``str`` :returns: The value of the requested option if it exists, or ``False`` """ # check CLI before defaults for opt in self.opts.cluster_options: if opt.name == option and opt.cluster in self.cluster_type: return opt.value # provide defaults otherwise for opt in self.options: if opt.name == option: return opt.value return False def add_default_ssh_key(self, key): """Some clusters generate and/or deploy well-known and consistent SSH keys across environments. If this is the case, the cluster profile may call this command so that subsequent node connections will use that key rather than prompting the user for one or a password. Note this will only function if collector is being run locally on the primary node. """ self.cluster_ssh_key = key def set_node_options(self, node): """If there is a need to set specific options on ONLY the non-primary nodes in a collection, override this method in the cluster profile and do that here. :param node: The non-primary node :type node: ``SoSNode`` """ pass def set_transport_type(self): """The default connection type used by sos collect is to leverage the local system's SSH installation using ControlPersist, however certain cluster types may want to use something else. Override this in a specific cluster profile to set the ``transport`` option according to what type of transport should be used. """ return 'control_persist' def set_primary_options(self, node): """If there is a need to set specific options in the sos command being run on the cluster's primary nodes, override this method in the cluster profile and do that here. :param node: The primary node :type node: ``SoSNode`` """ pass def check_node_is_primary(self, node): """In the event there are multiple primaries, or if the collect command is being run from a system that is technically capable of enumerating nodes but the cluster profiles needs to specify primary-specific options for other nodes, override this method in the cluster profile :param node: The node for the cluster to check :type node: ``SoSNode`` """ return node.address == self.primary.address def exec_primary_cmd(self, cmd, need_root=False, timeout=180, use_shell='auto'): """Used to retrieve command output from a (primary) node in a cluster :param cmd: The command to run :type cmd: ``str`` :param need_root: Does the command require root privileges :type need_root: ``bool`` :param timeout: Amount of time to allow cmd to run in seconds :type timeout: ``int`` :param use_shell: Does the command required execution within a shell? :type use_shell: ``auto`` or ``bool`` :returns: The output and status of `cmd` :rtype: ``dict`` """ res = self.primary.run_command(cmd, need_root=need_root, use_shell=use_shell, timeout=timeout) if res['output']: res['output'] = res['output'].replace('Password:', '') return res def setup(self): """ This MAY be used by a cluster to do prep work in case there are extra commands to be run even if a node list is given by the user, and thus get_nodes() would not be called """ pass def check_enabled(self): """ This may be overridden by clusters This is called by sos collect on each cluster type that exists, and is meant to return True when the cluster type matches a criteria that indicates that is the cluster type is in use. Only the first cluster type to determine a match is run :returns: ``True`` if the cluster profile should be used, or ``False`` :rtype: ``bool`` """ for pkg in self.packages: if self.primary.is_installed(pkg): return True return False def cleanup(self): """ This may be overridden by clusters Perform any necessary cleanup steps required by the cluster profile. This helps ensure that sos does make lasting changes to the environment in which we are running """ pass def get_nodes(self): """ This MUST be overridden by a cluster profile subclassing this class A cluster should use this method to return a list or string that contains all the nodes that a report should be collected from :returns: A list of node FQDNs or IP addresses :rtype: ``list`` or ``None`` """ raise NotImplementedError def _get_nodes(self): try: return self.format_node_list() except Exception as e: self.log_debug(f'Failed to get node list: {e}') return [] def get_node_label(self, node): """ Used by ``SosNode()`` to retrieve the appropriate label from the cluster as set by ``set_node_label()`` in the cluster profile. :param node: The name of the node to get a label for :type node: ``str`` :returns: The label to use for the node's report :rtype: ``str`` """ label = self.set_node_label(node) node.manifest.add_field('label', label) return label def set_node_label(self, node): # pylint: disable=unused-argument """This may be overridden by clusters profiles subclassing this class If there is a distinction between primaries and nodes, or types of nodes, then this can be used to label the sos report archives as needed """ return '' def format_node_list(self): """ Format the returned list of nodes from a cluster into a known format. This being a list that contains no duplicates :returns: A list of nodes, without extraneous entries from cmd output :rtype: ``list`` """ try: nodes = self.get_nodes() except Exception as err: raise Exception(f"Cluster failed to enumerate nodes: {err}") if isinstance(nodes, list): node_list = [n.strip() for n in nodes if n] elif isinstance(nodes, str): node_list = [n.split(',').strip() for n in nodes] else: raise Exception(f"Cluster returned unexpected node list: {nodes}") node_list = list(set(node_list)) for node in node_list: if node.startswith(('-', '_', '(', ')', '[', ']', '/', '\\')): node_list.remove(node) return node_list def _run_extra_cmd(self): """ Ensures that any files returned by a cluster's run_extra_cmd() method are properly typed as a list for iterative collection. If any of the files are an additional sos report (e.g. the ovirt db dump) then the md5 sum file is automatically added to the list """ files = [] try: res = self.run_extra_cmd() if res: if not isinstance(res, list): res = [res] for extra_file in res: extra_file = extra_file.strip() files.append(extra_file) if 'sosreport' in extra_file: files.append(extra_file + '.md5') except AttributeError: # run_extra_cmd() not defined for cluster profile pass return files sos-4.8.0/sos/collector/clusters/ceph.py0000664000175000017500000000454214660147624016723 0ustar arifarif# Copyright (C) 2022 Red Hat Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from sos.collector.clusters import Cluster class ceph(Cluster): """ This cluster profile is for Ceph Storage clusters, and is primarily built around Red Hat Ceph Storage 5. Nodes are enumerated via `cephadm`; if your Ceph deployment uses cephadm but is not RHCS 5, this profile may work as intended, but it is not currently guaranteed to do so. If you are using such an environment and this profile does not work for you, please file a bug report detailing what is failing. By default, all nodes in the cluster will be returned for collection. This may not be desirable, so users are encouraged to use the `labels` option to specify a colon-delimited set of ceph node labels to restrict the list of nodes to. For example, using `-c ceph.labels=osd:mgr` will return only nodes labeled with *either* `osd` or `mgr`. """ cluster_name = 'Ceph Storage Cluster' sos_plugins = [ 'ceph_common', ] sos_options = {'log-size': 50} packages = ('cephadm',) option_list = [ ('labels', '', 'Colon delimited list of labels to select nodes with') ] def get_nodes(self): self.nodes = [] ceph_out = self.exec_primary_cmd( 'cephadm shell -- ceph orch host ls --format json', need_root=True ) if not ceph_out['status'] == 0: self.log_error( f"Could not enumerate nodes via cephadm: {ceph_out['output']}" ) return self.nodes nodes = json.loads(ceph_out['output'].splitlines()[-1]) _labels = [lab for lab in self.get_option('labels').split(':') if lab] for node in nodes: if _labels and not any(_l in node['labels'] for _l in _labels): self.log_debug(f"{node} filtered from list due to labels") continue self.nodes.append(node['hostname']) return self.nodes # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/ovirt.py0000664000175000017500000001776414660147624017161 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import fnmatch from shlex import quote from sos.collector.clusters import Cluster ENGINE_KEY = '/etc/pki/ovirt-engine/keys/engine_id_rsa' class ovirt(Cluster): """ This cluster profile is for the oVirt/RHV project which provides for a virtualization cluster built ontop of KVM. Nodes enumerated will be hypervisors within the envrionment, not virtual machines running on those hypervisors. By default, ALL hypervisors within the environment are returned. This may be influenced by the 'cluster' and 'datacenter' cluster options, which will limit enumeration to hypervisors within the specific cluster and/or datacenter. The spm-only cluster option may also be used to only collect from hypervisors currently holding the SPM role. Optionally, to only collect an archive from manager and the postgresql database, use the no-hypervisors cluster option. By default, a second archive from the manager will be collected that is just the postgresql plugin configured in such a way that a dump of the manager's database that can be explored and restored to other systems will be collected. The ovirt profile focuses on the upstream, community ovirt project. The rhv profile is for Red Hat customers running RHV (formerly RHEV). The rhhi_virt profile is for Red Hat customers running RHV in a hyper-converged setup and enables gluster collections. """ 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'), ('cluster', '', 'Only collect from hosts in this cluster'), ('datacenter', '', 'Only collect from hosts in this datacenter'), ('no-hypervisors', False, 'Do not collect from hypervisors'), ('spm-only', False, 'Only collect from SPM host(s)') ] def _run_db_query(self, query): ''' Wrapper for running DB queries on the manager. Any scrubbing of the query should be done _before_ passing the query to this method. ''' cmd = f"{self.db_exec} {quote(query)}" return self.exec_primary_cmd(cmd, need_root=True) 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(f"WARNING: Cluster option \'{val}\' contains invalid" " characters. Using '%%' instead.") return '%' return val def _check_for_engine_keys(self): ''' Checks for the presence of the VDSM ssh keys the manager uses for communication with hypervisors. This only runs if we're locally on the RHV-M, *and* if no ssh-keys are called out on the command line, *and* no --password option is given. ''' if self.primary.local: if not any([self.opts.ssh_key, self.opts.password, self.opts.password_per_node]): if self.primary.file_exists(ENGINE_KEY): self.add_default_ssh_key(ENGINE_KEY) self.log_debug("Found engine SSH key. User command line" " does not specify a key or password, using" " engine key.") def setup(self): self.pg_pass = False if not self.get_option('no-database'): self.conf = self.parse_db_conf() self.format_db_cmd() self._check_for_engine_keys() def format_db_cmd(self): cluster = self._sql_scrub(self.get_option('cluster')) datacenter = self._sql_scrub(self.get_option('datacenter')) self.dbquery = ("SELECT host_name from vds where cluster_id in " "(select cluster_id FROM cluster WHERE name like " f"'{cluster}' and storage_pool_id in (SELECT id FROM " f"storage_pool WHERE name like '{datacenter}'))") if self.get_option('spm-only'): # spm_status is an integer with the following meanings # 0 - Normal (not SPM) # 1 - Contending (SPM election in progress, but is not SPM) # 2 - SPM self.dbquery += ' AND spm_status = 2' self.log_debug(f'Query command for ovirt DB set to: {self.dbquery}') def get_nodes(self): if self.get_option('no-hypervisors'): return [] res = self._run_db_query(self.dbquery) if res['status'] == 0: nodes = res['output'].splitlines()[2:-1] return [n.split('(')[0].strip() for n in nodes] raise Exception(f'database query failed, return code: {res["status"]}') def run_extra_cmd(self): if not self.get_option('no-database') and self.conf: return self.collect_database() return False def parse_db_conf(self): conf = {} engconf = '/etc/ovirt-engine/engine.conf.d/10-setup-database.conf' res = self.exec_primary_cmd(f'cat {engconf}', need_root=True) if res['status'] == 0: config = res['output'].splitlines() for line in config: try: k = str(line.split('=')[0]) v = str(line.split('=')[1].replace('"', '')) conf[k] = v except IndexError: # not a valid line to parse config values from, ignore pass return conf return False def collect_database(self): plugin = 'postgresql' sos_opt = ( f"-k {plugin}.dbname={self.conf['ENGINE_DB_DATABASE']} " f"-k {plugin}.dbhost={self.conf['ENGINE_DB_HOST']} " f"-k {plugin}.dbport={self.conf['ENGINE_DB_PORT']} " f"-k {plugin}.dbuser={self.conf['ENGINE_DB_USER']}" ) cmd = ( f"PGPASSWORD={self.conf['ENGINE_DB_PASSWORD']} /usr/sbin/sos " f"report --name=postgresql --batch -o postgresql {sos_opt}" ) db_sos = self.exec_primary_cmd(cmd, need_root=True) for line in db_sos['output'].splitlines(): if fnmatch.fnmatch(line, '*sosreport-*tar*'): _pg_dump = line.strip() self.primary.manifest.add_field('postgresql_dump', _pg_dump.split('/')[-1]) return _pg_dump self.log_error('Failed to gather database dump') return False class rhv(ovirt): cluster_name = 'Red Hat Virtualization' packages = ('rhevm', 'rhvm') sos_preset = 'rhv' def set_node_label(self, node): if node.address == self.primary.address: return 'manager' if node.is_installed('ovirt-node-ng-nodectl'): return 'rhvh' 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.primary.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['output'].splitlines()[2].strip() != '0' return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/juju.py0000664000175000017500000002114414660147624016756 0ustar arifarif# Copyright (c) 2023 Canonical Ltd., Chi Wai Chan # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import logging import json import re from sos.collector.clusters import Cluster from sos.utilities import sos_parse_version from sos.utilities import sos_get_command_output def _parse_option_string(strings=None): """Parse comma separated string.""" if not strings: return [] return [string.strip() for string in strings.split(",")] def _get_index(model_name): """Helper function to get Index. The reason why we need Index defined in function is because currently the collector.__init__ will load all the classes in this module and also Index. This will cause bug because it think Index is Cluster type. Also We don't want to provide a customized filter to remove Index class. """ class Index: """Index structure to help parse juju status output. Attributes apps, units and machines are dict which key is the app/unit/machine name and the value is list of targets which format are {model_name}:{machine_id}. """ def __init__(self, model_name): self.model_name: str = model_name self.apps = {} self.units = {} self.machines = {} self.ui_log = logging.getLogger("sos") def add_principals(self, juju_status): """Adds principal units to index.""" for app, app_info in juju_status["applications"].items(): nodes = [] units = app_info.get("units", {}) for unit, unit_info in units.items(): machine = unit_info["machine"] node = f"{self.model_name}:{machine}" self.units[unit] = [node] self.machines[machine] = [node] nodes.append(node) self.apps[app] = nodes def add_subordinates(self, juju_status): """Add subordinates to index. Since subordinates does not have units they need to be manually added. """ for app, app_info in juju_status["applications"].items(): subordinate_to = app_info.get("subordinate-to", []) for parent in subordinate_to: # If parent is missing if not self.apps.get(parent): self.ui_log.warning( f"Principal charm {parent} is missing" ) continue self.apps[app].extend(self.apps[parent]) # If parent's units is missing if "units" not in juju_status["applications"][parent]: self.ui_log.warning( f"Principal charm {parent} is missing units" ) continue units = juju_status["applications"][parent]["units"] for _, unit_info in units.items(): node = f"{self.model_name}:{unit_info['machine']}" for sub_key, _ in unit_info.get( "subordinates", {} ).items(): if sub_key.startswith(app + "/"): self.units[sub_key] = [node] def add_machines(self, juju_status): """Add machines to index. If model does not have any applications it needs to be manually added. """ for machine in juju_status["machines"].keys(): node = f"{self.model_name}:{machine}" self.machines[machine] = [node] return Index(model_name) class juju(Cluster): """ The juju cluster profile is intended to be used on juju managed clouds. It"s assumed that `juju` is installed on the machine where `sos` is called, and that the juju user has superuser privilege to the current controller. By default, the sos reports will be collected from all the applications in the current model. If necessary, you can filter the nodes by models / applications / units / machines with cluster options. Example: sos collect --cluster-type juju -c "juju.models=sos" -c "juju.apps=a,b,c" """ cmd = "juju" cluster_name = "Juju Managed Clouds" option_list = [ ("apps", "", "Filter node list by apps (comma separated regex)."), ("units", "", "Filter node list by units (comma separated string)."), ("models", "", "Filter node list by models (comma separated string)."), ( "machines", "", "Filter node list by machines (comma separated string).", ), ] def _cleanup_juju_output(self, output): """Remove leading characters before {.""" return re.sub(r"(^[^{]*)(.*)", "\\2", output, 0, re.MULTILINE) def _get_model_info(self, model_name): """Parse juju status output and return target dict. Here are couple helper functions to parse the juju principals units, subordinate units and machines. """ juju_status = self._execute_juju_status(model_name) index = _get_index(model_name=model_name) index.add_principals(juju_status) index.add_subordinates(juju_status) index.add_machines(juju_status) return index def _get_juju_version(self): """Grab the version of juju""" res = sos_get_command_output("juju version") return res['output'] def _execute_juju_status(self, model_name): model_option = f"-m {model_name}" if model_name else "" format_option = "--format json" juju_version = self._get_juju_version() if sos_parse_version(juju_version) > sos_parse_version("3"): format_option += " --no-color" status_cmd = f"{self.cmd} status {model_option} {format_option}" res = self.exec_primary_cmd(status_cmd) if not res["status"] == 0: raise Exception(f"'{status_cmd}' returned error: {res['status']}") juju_json_output = self._cleanup_juju_output((res["output"])) juju_status = None try: juju_status = json.loads(juju_json_output) except json.JSONDecodeError: raise Exception( "Juju output is not valid json format." f"Output: {juju_json_output}" ) return juju_status def _filter_by_pattern(self, key, patterns, model_info): """Filter with regex match.""" nodes = set() for pattern in patterns: for param, value in getattr(model_info, key).items(): if re.match(pattern, param): nodes.update(value or []) return nodes def _filter_by_fixed(self, key, patterns, model_info): """Filter with fixed match.""" nodes = set() for pattern in patterns: for param, value in getattr(model_info, key).items(): if pattern == param: nodes.update(value or []) return nodes def set_transport_type(self): """Dynamically change transport to 'juju'.""" return "juju" def get_nodes(self): """Get the machine numbers from `juju status`.""" models = _parse_option_string(self.get_option("models")) apps = _parse_option_string(self.get_option("apps")) units = _parse_option_string(self.get_option("units")) machines = _parse_option_string(self.get_option("machines")) filters = {"apps": apps, "units": units, "machines": machines} # Return empty nodes if no model and filter provided. if not any(filters.values()) and not models: return [] if not models: models = [""] # use current model by default nodes = set() for model in models: model_info = self._get_model_info(model) for key, resource in filters.items(): # Filter node by different policies if key == "apps": _nodes = self._filter_by_pattern(key, resource, model_info) else: _nodes = self._filter_by_fixed(key, resource, model_info) nodes.update(_nodes) return list(nodes) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/kubernetes.py0000664000175000017500000000350014660147624020144 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from shlex import quote from sos.collector.clusters import Cluster class kubernetes(Cluster): """ The kuberentes cluster profile is intended to be used on kubernetes clusters built from the upstream/source kubernetes (k8s) project. It is not intended for use with other projects or platforms that are built ontop of kubernetes. """ cluster_name = 'Community Kubernetes' packages = ('kubernetes-master',) sos_plugins = ['kubernetes'] sos_plugin_options = {'kubernetes.all': 'on'} cmd = 'kubectl' option_list = [ ('label', '', 'Filter node list to those with matching label'), ('role', '', 'Filter node list to those with matching role') ] def get_nodes(self): self.cmd += ' get nodes' if self.get_option('label'): self.cmd += f' -l {quote(self.get_option("label"))} ' res = self.exec_primary_cmd(self.cmd) if res['status'] == 0: nodes = [] roles = [x for x in self.get_option('role').split(',') if x] for nodeln in res['output'].splitlines()[1:]: node = nodeln.split() if not roles: nodes.append(node[0]) else: if node[2] in roles: nodes.append(node[0]) return nodes raise Exception('Node enumeration did not return usable output') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/saltstack.py0000664000175000017500000000616014660147624017773 0ustar arifarif# Copyright Red Hat 2022, Trevor Benson # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from shlex import quote from sos.collector.clusters import Cluster class saltstack(Cluster): """ The saltstack cluster profile is intended to be used on saltstack clusters (Salt Project). """ cluster_name = "Saltstack" packages = ("salt-master",) sos_plugins = ["saltmaster"] strict_node_list = True option_list = [ ("compound", "", "Filter node list to those matching compound"), ("glob", "", "Filter node list to those matching glob pattern"), ("grain", "", "Filter node list to those with matching grain"), ("minion_id_unresolvable", False, "Returns the FQDN grain of each" " minion in the node list when the minion ID is not a hostname."), ("nodegroup", "", "Filter node list to those matching nodegroup"), ("pillar", "", "Filter node list to those with matching pillar"), ("subnet", "", "Filter node list to those in subnet"), ] targeted = False node_cmd = "salt-run --out=pprint manage.status" def _parse_manage_status(self, output: str) -> list: nodes = [] salt_json_output = json.loads(output.replace("'", '"')) for _, value in salt_json_output.items(): nodes.extend(value) return nodes def _get_hostnames_from_grain(self, manage_status: dict) -> list: hostnames = [] for status, minions in manage_status.items(): if status == "down": self.log_warn(f"Node(s) {minions} are status down.") hostnames.extend(minions) else: for minion in minions: node_cmd = ( f"salt --out=newline_values_only {minion} " f"grains.get fqdn" ) hostnames.append( self.exec_primary_cmd(node_cmd)["output"].strip() ) return hostnames def _get_nodes(self) -> list: res = self.exec_primary_cmd(self.node_cmd) if res["status"] != 0: raise Exception("Node enumeration did not return usable output") if self.get_option("minion_id_unresolvable"): status = json.loads(res["output"].replace("'", '"')) return self._get_hostnames_from_grain(status) return self._parse_manage_status(res["output"]) def get_nodes(self): # Default to all online nodes for option in self.option_list: if option[0] != "minion_id_unresolvable": opt = self.get_option(option[0]) if opt: self.node_cmd += f" tgt={quote(opt)} tgt_type={option[0]}" break return self._get_nodes() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/ocp.py0000664000175000017500000004005614660147624016565 0ustar arifarif# Copyright Red Hat 2021, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from shlex import quote from sos.collector.clusters import Cluster from sos.utilities import is_executable class ocp(Cluster): """ This profile is for use with OpenShift Container Platform (v4) clusters instead of the kubernetes profile. This profile will favor using the `oc` transport type, which means it will leverage a locally installed `oc` binary. This is also how node enumeration is done. To instead use SSH to connect to the nodes, use the '--transport=control_persist' option. Thus, a functional `oc` binary for the user executing sos collect is required. Functional meaning that the user can run `oc` commands with clusterAdmin privileges. If this requires the use of a secondary configuration file, specify that path with the 'kubeconfig' cluster option. This config file will also be used on a single master node to perform API collections if the `with-api` option is enabled (default disabled). If no `kubeconfig` option is given, but `with-api` is enabled, the cluster profile will attempt to use a well-known default kubeconfig file if it is available on the host. Alternatively, provide a clusterAdmin access token either via the 'token' cluster option or, preferably, the SOSOCPTOKEN environment variable. By default, this profile will enumerate only master nodes within the cluster, and this may be changed by overriding the 'role' cluster option. To collect from all nodes in the cluster regardless of role, use the form -c ocp.role=''. Filtering nodes by a label applied to that node is also possible via the label cluster option, though be aware that this is _combined_ with the role option mentioned above. To avoid redundant collections of OCP API information (e.g. 'oc get' commands), this profile will attempt to enable the API collections on only a single master node. If the none of the master nodes have a functional 'oc' binary available, *and* the --no-local option is used, that means that no API data will be collected. """ cluster_name = 'OpenShift Container Platform v4' packages = ('openshift-hyperkube', 'openshift-clients') api_collect_enabled = False token = None project = 'sos-collect-tmp' oc_cluster_admin = None _oc_cmd = '' option_list = [ ('label', '', 'Colon delimited list of labels to select nodes with'), ('role', 'master', 'Colon delimited list of roles to filter on'), ('kubeconfig', '', 'Path to the kubeconfig file'), ('token', '', 'Service account token to use for oc authorization'), ('with-api', False, 'Collect OCP API data from a master node'), ('api-url', '', 'Alternate API URL of an external control-plane'), ] @property def oc_cmd(self): if not self._oc_cmd: self._oc_cmd = 'oc' if self.primary.host.in_container(): _oc_path = self.primary.run_command( 'which oc', chroot=self.primary.host.sysroot ) if _oc_path['status'] == 0: self._oc_cmd = os.path.join( self.primary.host.sysroot, _oc_path['output'].strip().lstrip('/') ) else: self.log_warn( "Unable to to determine PATH for 'oc' command, " "node enumeration may fail." ) self.log_debug( f"Locating 'oc' failed: {_oc_path['output']}") if self.get_option('kubeconfig'): self._oc_cmd += " --kubeconfig " \ f"{self.get_option('kubeconfig')}" self.log_debug(f"oc base command set to {self._oc_cmd}") return self._oc_cmd def fmt_oc_cmd(self, cmd): """Format the oc command to optionall include the kubeconfig file if one is specified """ return f"{self.oc_cmd} {cmd}" def _attempt_oc_login(self): """Attempt to login to the API using the oc command using a provided token """ _res = self.exec_primary_cmd( self.fmt_oc_cmd("login --insecure-skip-tls-verify=True " f"--token={self.token} " f"{self.get_option('api-url')}") ) return _res['status'] == 0 def check_enabled(self): if super().check_enabled(): return True self.token = self.get_option('token') or os.getenv('SOSOCPTOKEN', None) if self.token: self._attempt_oc_login() _who = self.fmt_oc_cmd('whoami') return self.exec_primary_cmd(_who)['status'] == 0 def setup(self): """Create the project that we will be executing in for any nodes' collection via a container image """ if not self.set_transport_type() == 'oc': return None out = self.exec_primary_cmd(self.fmt_oc_cmd("auth can-i '*' '*'")) self.oc_cluster_admin = out['status'] == 0 if not self.oc_cluster_admin: self.log_debug("Check for cluster-admin privileges returned false," " cannot create project in OCP cluster") raise Exception("Insufficient permissions to create temporary " "collection project.\nAborting...") self.log_info(f"Creating new temporary project '{self.project}'") ret = self.exec_primary_cmd( self.fmt_oc_cmd(f"new-project {self.project}") ) if ret['status'] == 0: self._label_sos_project() return True self.log_debug(f"Failed to create project: {ret['output']}") raise Exception("Failed to create temporary project for collection. " "\nAborting...") def _label_sos_project(self): """Add pertinent labels to the temporary project we've created so that our privileged containers can properly run. """ labels = [ "security.openshift.io/scc.podSecurityLabelSync=false", "pod-security.kubernetes.io/enforce=privileged" ] for label in labels: ret = self.exec_primary_cmd( self.fmt_oc_cmd( f"label namespace {self.project} {label} --overwrite" ) ) if not ret['status'] == 0: raise Exception( f"Error applying namespace labels: {ret['output']}" ) def cleanup(self): """Remove the project we created to execute within """ if self.project: try: ret = self.exec_primary_cmd( self.fmt_oc_cmd(f"delete project {self.project}"), timeout=30 ) if not ret['status'] == 0: self.log_error( f"Error deleting temporary project: {ret['output']}" ) ret = self.exec_primary_cmd( self.fmt_oc_cmd( f"wait namespace/{self.project} --for=delete " f"--timeout=30s" ) ) if not ret['status'] == 0: self.log_error( f"Error waiting for temporary project to be deleted: " f"{ret['output']}" ) except Exception as err: self.log_error( f"Failed attempting to remove temporary project " f"'sos-collect-tmp': {err}\n" f"Please manually remove the temporary project." ) # don't leave the config on a non-existing project self.exec_primary_cmd(self.fmt_oc_cmd("project default")) self.project = None return True def _build_dict(self, nodelist): """From the output of get_nodes(), construct an easier-to-reference dict of nodes that will be used in determining labels, primary status, etc... :param nodelist: The split output of `oc get nodes` :type nodelist: ``list`` :returns: A dict of nodes with `get nodes` columns as keys :rtype: ``dict`` """ nodes = {} if 'NAME' in nodelist[0]: # get the index of the fields statline = nodelist.pop(0).split() idx = {} for state in ['status', 'roles', 'version', 'os-image']: try: idx[state] = statline.index(state.upper()) except Exception: # label is not available, which is not fatal for our dict # construction here pass for node in nodelist: _node = node.split() nodes[_node[0]] = {} for column, value in idx.items(): nodes[_node[0]][column] = _node[value] return nodes def set_transport_type(self): if self.opts.transport != 'auto': return self.opts.transport if is_executable('oc', sysroot=self.primary.host.sysroot): return 'oc' self.log_info("Local installation of 'oc' not found or is not " "correctly configured. Will use ControlPersist.") self.ui_log.warning( "Preferred transport 'oc' not available, will fallback to SSH." ) if not self.opts.batch: input("Press ENTER to continue connecting with SSH, or Ctrl+C to" "abort.") return 'control_persist' def get_nodes(self): nodes = [] self.node_dict = {} cmd = 'get nodes -o wide' if self.get_option('label'): labels = ','.join(self.get_option('label').split(':')) cmd += f" -l {quote(labels)}" res = self.exec_primary_cmd(self.fmt_oc_cmd(cmd)) if res['status'] == 0: if self.get_option('role') == 'master': self.log_warn("NOTE: By default, only master nodes are listed." "\nTo collect from all/more nodes, override the " "role option with '-c ocp.role=role1:role2'") roles = list(self.get_option('role').split(':')) self.node_dict = self._build_dict(res['output'].splitlines()) for node_name, node in self.node_dict.items(): if roles: for role in roles: if role in node['roles']: nodes.append(node_name) break else: nodes.append(node_name) else: msg = "'oc' command failed" if 'Missing or incomplete' in res['output']: msg = ("'oc' failed due to missing kubeconfig on primary node." " Specify one via '-c ocp.kubeconfig='") raise Exception(msg) return nodes def set_node_label(self, node): if node.address not in self.node_dict: return '' for label in ['master', 'worker']: if label in self.node_dict[node.address]['roles']: return label return '' def check_node_is_primary(self, node): if node.address not in self.node_dict: return False return 'master' in self.node_dict[node.address]['roles'] def _toggle_api_opt(self, node, use_api): """In earlier versions of sos, the openshift plugin option that is used to toggle the API collections was called `no-oc` rather than `with-api`. This older plugin option had the inverse logic of the current `with-api` option. Use this to toggle the correct plugin option given the node's sos version. Note that the use of version 4.2 here is tied to the RHEL release (the only usecase for this cluster profile) rather than the upstream version given the backports for that downstream. :param node: The node being inspected for API collections :type node: ``SoSNode`` :param use_api: Should this node enable API collections? :type use_api: ``bool`` """ if node.check_sos_version('4.2-16'): _opt = 'with-api' _val = 'on' if use_api else 'off' else: _opt = 'no-oc' _val = 'off' if use_api else 'on' node.plugopts.append(f"openshift.{_opt}={_val}") def set_primary_options(self, node): node.enable_plugins.append('openshift') if not self.get_option('with-api'): self._toggle_api_opt(node, False) return if self.api_collect_enabled: # a primary has already been enabled for API collection, disable # it among others self._toggle_api_opt(node, False) else: # running in a container, so reference the /host mount point master_kube = ( '/host/etc/kubernetes/static-pod-resources/' 'kube-apiserver-certs/secrets/node-kubeconfigs/' 'localhost.kubeconfig' ) _optconfig = self.get_option('kubeconfig') if _optconfig and not _optconfig.startswith('/host'): _optconfig = '/host/' + _optconfig _kubeconfig = _optconfig or master_kube _oc_cmd = 'oc' if node.host.containerized: _oc_cmd = '/host/bin/oc' # when run from a container, the oc command does not inherit # the default config, so if it's present then pass it here to # detect a funcitonal oc command. This is sidestepped in sos # report by being able to chroot the `oc` execution which we # cannot do remotely if node.file_exists('/root/.kube/config', need_root=True): _oc_cmd += ' --kubeconfig /host/root/.kube/config' can_oc = node.run_command(f"{_oc_cmd} whoami", use_container=node.host.containerized, # container is available only to root # and if rhel, need to run sos as root # anyways which will run oc as root need_root=True) if can_oc['status'] == 0: # the primary node can already access the API self._toggle_api_opt(node, True) self.api_collect_enabled = True elif self.token: node.sos_env_vars['SOSOCPTOKEN'] = self.token self._toggle_api_opt(node, True) self.api_collect_enabled = True elif node.file_exists(_kubeconfig): # if the file exists, then the openshift sos plugin will use it # if the with-api option is turned on if _kubeconfig != master_kube: node.plugopts.append( f"openshift.kubeconfig={_kubeconfig}" ) self._toggle_api_opt(node, True) self.api_collect_enabled = True if self.api_collect_enabled: msg = (f"API collections will be performed on {node.address}\n" "Note: API collections may extend runtime by 10s of " "minutes\n") self.soslog.info(msg) self.ui_log.info(msg) def set_node_options(self, node): # don't attempt OC API collections on non-primary nodes self._toggle_api_opt(node, False) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/jbon.py0000664000175000017500000000222314660147624016726 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.collector.clusters import Cluster class jbon(Cluster): """ Used when --cluster-type=none (or jbon) to avoid cluster checks, and just use the provided --nodes list. Using this profile will skip any and all operations that a cluster profile normally performs, and will not set any plugins, plugin options, or presets for the sos report generated on the nodes provided by --nodes. """ cluster_name = 'Just a Bunch Of Nodes (no cluster)' packages = None def get_nodes(self): return [] 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 # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/satellite.py0000664000175000017500000000314214660147624017765 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from shlex import quote from sos.collector.clusters import Cluster class satellite(Cluster): """ This profile is specifically for Red Hat Satellite 6, and not earlier releases of Satellite. While note technically a 'cluster' in the traditional sense, Satellite does provide for 'capsule' nodes which is what this profile aims to enumerate beyond the 'primary' Satellite system. """ cluster_name = 'Red Hat Satellite 6' packages = ('satellite', 'satellite-installer') def _psql_cmd(self, query): _cmd = "su postgres -c %s" _dbcmd = "psql foreman -c %s" return _cmd % quote(_dbcmd % quote(query)) def get_nodes(self): cmd = self._psql_cmd('copy (select name from smart_proxies) to stdout') res = self.exec_primary_cmd(cmd, need_root=True) if res['status'] == 0: nodes = [ n.strip() for n in res['output'].splitlines() if 'could not change directory' not in n ] return nodes return [] def set_node_label(self, node): if node.address == self.primary.address: return 'satellite' return 'capsule' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/clusters/openstack.py0000664000175000017500000000541614660147624017774 0ustar arifarif# Copyright Red Hat 2022, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import yaml from sos.collector.clusters import Cluster INVENTORY = "/var/lib/mistral/overcloud/tripleo-ansible-inventory.yaml" class rhosp(Cluster): """ This cluster profile is for use with Red Hat OpenStack Platform environments. Different types of nodes may be enumerated by toggling the various profile options such as Controllers and Compute nodes. By default, only Controller nodes are enumerated. Node enumeration is done by inspecting the ansible inventory file used for deployment of the environment. This is canonically located at /var/lib/mistral/overcloud/tripleo-ansible-inventory.yaml. Similarly, the presence of this file on the primary node is what triggers the automatic enablement of this profile. Special consideration should be taken for where `sos collect` is being run from, in that the hostnames of the enumerated nodes must be resolveable from that system - not just from the primary node from which those nodes are discovered. If this is not possible, consider enabling the `use-ip` cluster option to instead have this profile source the IP addresses of the nodes in question. """ cluster_name = 'Red Hat OpenStack Platform' option_list = [ ('use-ip', False, 'use IP addresses instead of hostnames to connect'), ('controller', True, 'collect reports from controller nodes'), ('compute', False, 'collect reports from compute nodes') ] def check_enabled(self): return self.primary.file_exists(INVENTORY, need_root=True) def get_nodes(self): _nodes = [] _addr_field = ('external_ip' if self.get_option('use-ip') else 'ctlplane_hostname') try: _inv = yaml.safe_load(self.primary.read_file(INVENTORY)) except Exception as err: self.log_info(f"Error parsing yaml: {err}") raise Exception("Could not parse yaml for node addresses") try: for _t in ['Controller', 'Compute']: # fields are titled in the yaml, but our opts are lowercase if self.get_option(_t.lower()): for host in _inv[_t]['hosts'].keys(): _nodes.append(_inv[_t]['hosts'][host][_addr_field]) except Exception as err: self.log_error(f"Error getting {_t} host addresses: {err}") return _nodes sos-4.8.0/sos/collector/__init__.py0000664000175000017500000017301714660147624015703 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # pylint: disable=too-many-locals,too-many-branches import fnmatch import inspect import json import os import random import re import string import socket import shutil import sys from datetime import datetime from concurrent.futures import ThreadPoolExecutor from getpass import getpass from pathlib import Path from shlex import quote from textwrap import fill from sos.cleaner import SoSCleaner from sos.collector.sosnode import SosNode from sos.options import ClusterOption, str_to_bool from sos.component import SoSComponent from sos.utilities import bold from sos import __version__ COLLECTOR_CONFIG_DIR = '/etc/sos/groups.d' class SoSCollector(SoSComponent): """ sos collect, or SoS Collector, is the formerly standalone sos-collector project, brought into sos natively in 4.0 and later. It is meant to collect sos reports from an arbitrary number of remote nodes, as well as the localhost, at the same time. These nodes may be either user defined, defined by some clustering software, or both. For cluster defined lists of nodes, cluster profiles exist that not only define how these node lists are generated but may also influence the sos report command run on nodes depending upon their role within the cluster. Nodes are connected to via a 'transport' which defaults to the use of OpenSSH's Control Persist feature. Other transport types are available, and may be specifically linked to use with a certain cluster profile (or, at minimum, a node within a certain cluster type even if that profile is not used). sos collect may be run from either a node within the cluster that is capable of enumerating/discovering the other cluster nodes, or may be run from a user's workstation and instructed to first connect to such a node via the --primary option. If run in the latter manner, users will likely want to use the --no-local option, as by default sos collect will also collect an sos report locally. Users should expect this command to result in a tarball containing one or more sos report archives on the system that sos collect was executed on. """ desc = 'Collect an sos report from multiple nodes simultaneously' arg_defaults = { 'all_logs': False, 'alloptions': False, 'allow_system_changes': False, 'become_root': False, 'case_id': False, 'chroot': 'auto', 'clean': False, 'cluster_options': [], 'cluster_type': None, 'container_runtime': 'auto', 'domains': [], 'disable_parsers': [], 'enable_plugins': [], 'encrypt_key': '', 'encrypt_pass': '', 'group': None, 'image': '', 'force_pull_image': True, 'skip_cleaning_files': [], 'jobs': 4, 'journal_size': 0, 'keywords': [], 'keyword_file': None, 'keep_binary_files': False, 'label': '', 'list_options': False, 'log_size': 0, 'low_priority': False, 'map_file': '/etc/sos/cleaner/default_mapping', 'primary': '', 'namespaces': None, 'nodes': [], 'no_env_vars': False, 'no_local': False, 'nopasswd_sudo': False, 'no_pkg_check': False, 'no_update': False, 'only_plugins': [], 'password': False, 'password_per_node': False, 'plugopts': [], 'plugin_timeout': None, 'cmd_timeout': None, 'preset': '', 'registry_user': None, 'registry_password': None, 'registry_authfile': None, 'save_group': '', 'since': '', 'skip_commands': [], 'skip_files': [], 'skip_plugins': [], 'ssh_key': '', 'ssh_port': 22, 'ssh_user': 'root', 'timeout': 600, 'transport': 'auto', 'verify': False, 'usernames': [], 'upload': False, 'upload_url': None, 'upload_directory': None, 'upload_user': None, 'upload_pass': None, 'upload_method': 'auto', 'upload_no_ssl_verify': False, 'upload_protocol': 'auto', 'upload_s3_endpoint': None, 'upload_s3_region': None, 'upload_s3_bucket': None, 'upload_s3_access_key': None, 'upload_s3_secret_key': None, 'upload_s3_object_prefix': None } def __init__(self, parser, parsed_args, cmdline_args): super().__init__(parser, parsed_args, cmdline_args) os.umask(0o77) self.client_list = [] self.node_list = [] self.primary = None self.retrieved = 0 self.cluster = None self.cluster_type = None # add manifest section for collect self.manifest.components.add_section('collect') # shorthand reference self.collect_md = self.manifest.components.collect # placeholders in manifest organization self.collect_md.add_field('cluster_type', 'none') self.collect_md.add_list('node_list') # add a place to set/get the sudo password, but do not expose it via # the CLI, because security is a thing setattr(self.opts, 'sudo_pw', '') # get the local hostname and addresses to filter from results later self.hostname = socket.gethostname() try: self.ip_addrs = list({ i[4][0] for i in socket.getaddrinfo(socket.gethostname(), None) }) except Exception: # this is almost always a DNS issue with reverse resolution # set a safe fallback and log the issue self.log_error( "Could not get a list of IP addresses from this hostnamne. " "This may indicate a DNS issue in your environment" ) self.ip_addrs = ['127.0.0.1'] self._parse_options() self.clusters = self.load_clusters() if not self.opts.list_options: try: self.parse_node_strings() self.parse_cluster_options() self.log_debug(f'Executing {" ".join(s for s in sys.argv)}') self.log_debug( f"Found cluster profiles: {self.clusters.keys()}") self.verify_cluster_options() except KeyboardInterrupt: self.exit('Exiting on user cancel', 130) def load_clusters(self): """Loads all cluster types supported by the local installation for future comparison and/or use """ import sos.collector.clusters package = sos.collector.clusters supported_clusters = {} clusters = self._load_modules(package, 'clusters') for cluster in clusters: supported_clusters[cluster[0]] = cluster[1](self.commons) return supported_clusters @classmethod def _load_modules(cls, package, submod): """Helper to import cluster and host types""" modules = [] for path in package.__path__: if os.path.isdir(path): modules.extend(cls._find_modules_in_path(path, submod)) return modules @classmethod def _find_modules_in_path(cls, path, modulename): """Given a path and a module name, find everything that can be imported and then import it path - the filesystem path of the package modulename - the name of the module in the package E.G. a path of 'clusters', and a modulename of 'ovirt' equates to importing sos.collector.clusters.ovirt """ modules = [] if os.path.exists(path): for pyfile in sorted(os.listdir(path)): if not pyfile.endswith('.py'): continue if '__' in pyfile: continue fname, _ = os.path.splitext(pyfile) modname = f'sos.collector.{modulename}.{fname}' modules.extend(cls._import_modules(modname)) return modules @classmethod def _import_modules(cls, modname): """Import and return all found classes in a module""" mod_short_name = modname.split('.')[2] try: module = __import__(modname, globals(), locals(), [mod_short_name]) except ImportError as e: print(f'Error while trying to load module {modname}: ' f' {e.__class__.__name__}') raise e modules = inspect.getmembers(module, inspect.isclass) for mod in modules.copy(): if mod[0] in ('SosHost', 'Cluster'): modules.remove(mod) return modules def parse_node_strings(self): """Parses the given --nodes option(s) to properly format the regex list that we use. We cannot blindly split on ',' chars since it is a valid regex character, so we need to scan along the given strings and check at each comma if we should use the preceeding string by itself or not, based on if there is a valid regex at that index. """ if not self.opts.nodes: return nodes = [] if not isinstance(self.opts.nodes, list): self.opts.nodes = [self.opts.nodes] for node in self.opts.nodes: idxs = [i for i, m in enumerate(node) if m == ','] idxs.append(len(node)) start = 0 pos = 0 for idx in idxs: try: pos = idx reg = node[start:idx] re.compile(re.escape(reg)) # make sure we aren't splitting a regex value if '[' in reg and ']' not in reg: continue nodes.append(reg.lstrip(',')) start = idx except re.error: continue if pos != len(node): nodes.append(node[pos+1:]) self.opts.nodes = nodes @classmethod def add_parser_options(cls, parser): # Add the supported report passthru options to a group for logical # grouping in --help display sos_grp = parser.add_argument_group( 'Report Passthru Options', 'These options control how report is run on nodes' ) sos_grp.add_argument('-a', '--alloptions', action='store_true', help='Enable all sos report options') sos_grp.add_argument('--all-logs', action='store_true', help='Collect logs regardless of size') sos_grp.add_argument('--allow-system-changes', action='store_true', default=False, help=('Allow sos report to run commands that may ' 'alter system state')) sos_grp.add_argument('--chroot', default='', choices=['auto', 'always', 'never'], help="chroot executed commands to SYSROOT") sos_grp.add_argument("--container-runtime", default="auto", help="Default container runtime to use for " "collections. 'auto' for policy control.") sos_grp.add_argument('-e', '--enable-plugins', action="extend", help='Enable specific plugins for sos report') sos_grp.add_argument('--journal-size', type=int, default=0, help='Limit the size of journals in MiB') sos_grp.add_argument('-k', '--plugin-option', '--plugopts', action="extend", dest='plugopts', help='Plugin option as plugname.option=value') sos_grp.add_argument('--log-size', default=0, type=int, help='Limit the size of individual logs ' '(not journals) in MiB') sos_grp.add_argument('--low-priority', action='store_true', default=False, help='Run reports as low priority') sos_grp.add_argument('-n', '--skip-plugins', action="extend", help='Skip these plugins') sos_grp.add_argument('-o', '--only-plugins', action="extend", default=[], help='Run these plugins only') sos_grp.add_argument('--namespaces', default=None, help='limit number of namespaces to collect ' 'output for - 0 means unlimited') sos_grp.add_argument('--no-env-vars', action='store_true', default=False, help='Do not collect env vars in sos reports') sos_grp.add_argument('--plugin-timeout', type=int, default=None, help='Set the global plugin timeout value') sos_grp.add_argument('--cmd-timeout', type=int, default=None, help='Set the global command timeout value') sos_grp.add_argument('--since', default=None, help=('Escapes archived files older than date. ' 'This will also affect --all-logs. ' 'Format: YYYYMMDD[HHMMSS]')) sos_grp.add_argument('--skip-commands', default=[], action='extend', dest='skip_commands', help="do not execute these commands") sos_grp.add_argument('--skip-files', default=[], action='extend', dest='skip_files', help="do not collect these files") sos_grp.add_argument('--verify', action="store_true", help='perform pkg verification during collection') # Add the collector specific options to a separate group to keep # everything organized collect_grp = parser.add_argument_group( 'Collector Options', 'These options control how collect runs locally' ) collect_grp.add_argument('-b', '--become', action='store_true', dest='become_root', help='Become root on the remote nodes') collect_grp.add_argument('--case-id', help='Specify case number') collect_grp.add_argument('--cluster-type', help='Specify a type of cluster profile') collect_grp.add_argument('-c', '--cluster-option', dest='cluster_options', action='append', help=('Specify a cluster options used by a ' 'profile and takes the form of ' 'cluster.option=value')) collect_grp.add_argument('--group', default=None, help='Use a predefined group JSON file') collect_grp.add_argument('--save-group', default='', help='Save a resulting node list to a group') collect_grp.add_argument('--image', help=('Specify the container image to use for' ' containerized hosts.')) collect_grp.add_argument('--force-pull-image', '--pull', default=True, choices=(True, False), type=str_to_bool, help='Force pull the container image even if ' 'it already exists on the host') collect_grp.add_argument('--registry-user', default=None, help='Username to authenticate to the ' 'registry with for pulling an image') collect_grp.add_argument('--registry-password', default=None, help='Password to authenticate to the ' 'registry with for pulling an image') collect_grp.add_argument('--registry-authfile', default=None, help='Use this authfile to provide registry ' 'authentication when pulling an image') collect_grp.add_argument('-i', '--ssh-key', help='Specify an ssh key') collect_grp.add_argument('-j', '--jobs', default=4, type=int, help='Number of concurrent nodes to collect') collect_grp.add_argument('-l', '--list-options', action="store_true", help='List options available for profiles') collect_grp.add_argument('--label', help='Assign a label to the archives') collect_grp.add_argument('--primary', '--manager', '--controller', dest='primary', default='', help='Specify a primary node for cluster ' 'enumeration') collect_grp.add_argument('--nopasswd-sudo', action='store_true', help='Use passwordless sudo on nodes') collect_grp.add_argument('--nodes', action="append", help=('Provide a comma delimited list of ' 'nodes, or a regex to match against')) collect_grp.add_argument('--no-pkg-check', action='store_true', help=('Do not run package checks. Use this ' 'with --cluster-type if there are rpm ' 'or apt issues on node')) collect_grp.add_argument('--no-local', action='store_true', help='Do not collect a report from localhost') collect_grp.add_argument('-p', '--ssh-port', type=int, help='Specify SSH port for all nodes') collect_grp.add_argument('--password', action='store_true', default=False, help='Prompt for user password for nodes') collect_grp.add_argument('--password-per-node', action='store_true', default=False, help='Prompt for password for each node') collect_grp.add_argument('--preset', default='', required=False, help='Specify a sos preset to use') collect_grp.add_argument('--ssh-user', help='Specify an SSH user. Default root') collect_grp.add_argument('--timeout', type=int, required=False, help='Timeout for sos report on each node.') collect_grp.add_argument('--transport', default='auto', type=str, help='Remote connection transport to use') collect_grp.add_argument("--upload", action="store_true", default=False, help="Upload archive to a policy-default " "location") collect_grp.add_argument("--upload-url", default=None, help="Upload the archive to specified server") collect_grp.add_argument("--upload-directory", default=None, help="Specify upload directory for archive") collect_grp.add_argument("--upload-user", default=None, help="Username to authenticate with") collect_grp.add_argument("--upload-pass", default=None, help="Password to authenticate with") collect_grp.add_argument("--upload-method", default='auto', choices=['auto', 'put', 'post'], help="HTTP method to use for uploading") collect_grp.add_argument("--upload-no-ssl-verify", default=False, action='store_true', help="Disable SSL verification for upload url" ) collect_grp.add_argument("--upload-s3-endpoint", default=None, help="Endpoint to upload to for S3 bucket") collect_grp.add_argument("--upload-s3-region", default=None, help="Region for the S3 bucket") collect_grp.add_argument("--upload-s3-bucket", default=None, help="Name of the S3 bucket to upload to") collect_grp.add_argument("--upload-s3-access-key", default=None, help="Access key for the S3 bucket") collect_grp.add_argument("--upload-s3-secret-key", default=None, help="Secret key for the S3 bucket") collect_grp.add_argument("--upload-s3-object-prefix", default=None, help="Prefix for the S3 object/key") collect_grp.add_argument("--upload-protocol", default='auto', choices=['auto', 'https', 'ftp', 'sftp', 's3'], help="Manually specify the upload protocol") # Group the cleaner options together cleaner_grp = parser.add_argument_group( 'Cleaner/Masking Options', 'These options control how data obfuscation is performed' ) cleaner_grp.add_argument('--clean', '--cleaner', '--mask', dest='clean', default=False, action='store_true', help='Obfuscate sensitive information') cleaner_grp.add_argument('--keep-binary-files', default=False, action='store_true', dest='keep_binary_files', help='Keep unprocessable binary files in the ' 'archive instead of removing them') cleaner_grp.add_argument('--domains', dest='domains', default=[], action='extend', help='Additional domain names to obfuscate') cleaner_grp.add_argument('--disable-parsers', action='extend', default=[], dest='disable_parsers', help=('Disable specific parsers, so that ' 'those elements are not obfuscated')) cleaner_grp.add_argument('--skip-cleaning-files', '--skip-masking-files', action='extend', default=[], dest='skip_cleaning_files', help=('List of files to skip/ignore during ' 'cleaning. Globs are supported.')) cleaner_grp.add_argument('--keywords', action='extend', default=[], dest='keywords', help='List of keywords to obfuscate') cleaner_grp.add_argument('--keyword-file', default=None, dest='keyword_file', help='Provide a file a keywords to obfuscate') cleaner_grp.add_argument('--no-update', action='store_true', default=False, dest='no_update', help='Do not update the default cleaner map') cleaner_grp.add_argument('--map-file', dest='map_file', default='/etc/sos/cleaner/default_mapping', help=('Provide a previously generated mapping' ' file for obfuscation')) cleaner_grp.add_argument('--usernames', dest='usernames', default=[], action='extend', help='List of usernames to obfuscate') @classmethod def display_help(cls, section): section.set_title('SoS Collect Detailed Help') section.add_text(cls.__doc__) hsections = { 'collect.clusters': 'Information on cluster profiles', 'collect.clusters.$cluster': 'Specific profile information', 'collect.transports': 'Information on how connections are made', 'collect.transports.$transport': 'Specific transport information' } section.add_text( 'The following help sections may be of further interest:\n' ) for hsec, value in hsections.items(): section.add_text( f"{' ':>8}{bold(hsec):<40}{value:<30}", newline=False ) def exit(self, msg=None, error=0, force=False): """Used to terminate and ensure all cleanup is done, setting the exit code as specified if required. :param msg: Log the provided message as an error :type msg: ``str`` :param error: The exit code to use when terminating :type error: ``int`` :param force: Use os.exit() to break out of nested threads if needed :type force: ``bool`` """ if self.cluster: self.cluster.cleanup() if msg: self.log_error(msg) try: self.close_all_connections() except Exception: self.log_warn("Warning: Failed to close all remote connections") if error != 130: # keep the tempdir around when a user issues a keyboard interrupt # like we do for report self.cleanup() if not force: sys.exit(error) else: os._exit(error) def _parse_options(self): """From commandline options, defaults, etc... build a set of commons to hand to other collector mechanisms """ self.commons = { 'cmdlineopts': self.opts, 'need_sudo': self.opts.ssh_user != 'root', 'tmpdir': self.tmpdir, 'hostlen': max(len(self.opts.primary), len(self.hostname)), 'policy': self.policy } def parse_cluster_options(self): opts = [] if not isinstance(self.opts.cluster_options, list): self.opts.cluster_options = [self.opts.cluster_options] if self.opts.cluster_options: for option in self.opts.cluster_options: cluster = option.split('.')[0] name = option.split('.')[1].split('=')[0] try: # there are no instances currently where any cluster option # should contain a legitimate space. value = option.split('=')[1].split()[0] except IndexError: # conversion to boolean is handled during validation value = 'True' opts.append( ClusterOption(name, value, value.__class__, cluster) ) self.opts.cluster_options = opts def verify_cluster_options(self): """Verify that requested cluster options exist""" if self.opts.cluster_options: for opt in self.opts.cluster_options: match = False for clust, value in self.clusters.items(): for option in value.options: if opt.name == option.name and opt.cluster == clust: match = True opt.value = self._validate_option(option, opt) break if not match: self.exit('Unknown cluster option provided: ' f'{opt.cluster}.{opt.name}', 1) def _validate_option(self, default, cli): """Checks to make sure that the option given on the CLI is valid. Valid in this sense means that the type of value given matches what a cluster profile expects (str for str, bool for bool, etc). For bool options, this will also convert the string equivalent to an actual boolean value """ if not default.opt_type == bool: if not default.opt_type == cli.opt_type: msg = "Invalid option type for %s. Expected %s got %s" self.exit(msg % (cli.name, default.opt_type, cli.opt_type), 1) return cli.value val = cli.value.lower() if val not in ['true', 'on', 'yes', 'false', 'off', 'no']: msg = ("Invalid value for %s. Accepted values are: 'true', " "'false', 'on', 'off', 'yes', 'no'.") self.exit(msg % cli.name, 1) return val in ['true', 'on', 'yes'] def log_info(self, msg): """Log info messages to both console and log file""" self.soslog.info(msg) def log_warn(self, msg): """Log warn messages to both console and log file""" self.soslog.warning(msg) def log_error(self, msg): """Log error messages to both console and log file""" self.soslog.error(msg) def log_debug(self, msg): """Log debug message to both console and log file""" caller = inspect.stack()[1][3] msg = f'[sos_collector:{caller}] {msg}' self.soslog.debug(msg) def list_options(self): """Display options for available clusters""" sys.stdout.write('\nThe following clusters are supported by this ' 'installation\n') sys.stdout.write('Use the short name with --cluster-type or cluster ' 'options (-c)\n\n') for cluster in sorted(self.clusters): sys.stdout.write( f" {cluster:<15} {self.clusters[cluster].cluster_name:30}\n" ) _opts = {} for _, value in self.clusters.items(): for opt in value.options: if opt.name not in _opts.keys(): _opts[opt.name] = opt else: for clust in opt.cluster: if clust not in _opts[opt.name].cluster: _opts[opt.name].cluster.append(clust) sys.stdout.write('\nThe following cluster options are available:\n\n') sys.stdout.write( f" {'Cluster':25} {'Option Name':15} {'Type':<10} {'Default':10} " f"{'Description':<}\n" ) for _opt in sorted(_opts, key=lambda x: _opts[x].cluster): opt = _opts[_opt] optln = ( f" {', '.join(c for c in sorted(opt.cluster)):25} " f"{opt.name:15} {opt.opt_type.__name__:<10} " f"{str(opt.value):<10} {opt.description:<10}\n" ) sys.stdout.write(optln) sys.stdout.write('\nOptions take the form of cluster.name=value' '\nE.G. "ovirt.no-database=True" or ' '"pacemaker.offline=False"\n') def delete_tmp_dir(self): """Removes the temp directory and all collected sos reports""" shutil.rmtree(self.tmpdir) def _get_archive_name(self): """Generates a name for the tarball archive""" nstr = 'sos-collector' if self.opts.label: nstr += f'-{self.opts.label}' if self.opts.case_id: nstr += f'-{self.opts.case_id}' dt = datetime.strftime(datetime.now(), '%Y-%m-%d') try: string.lowercase = string.ascii_lowercase except NameError as err: self.log_debug(f"Could not cast to ascii_lowercase: {err}") rand = ''.join(random.choice(string.lowercase) for x in range(5)) return f'{nstr}-{dt}-{rand}' def _get_archive_path(self): """Returns the path, including filename, of the tarball we build that contains the collected sos reports """ self.arc_name = self._get_archive_name() compr = 'gz' return self.tmpdir + '/' + self.arc_name + '.tar.' + compr def _fmt_msg(self, msg): width = 80 _fmt = '' for line in msg.splitlines(): _fmt = _fmt + fill(line, width, replace_whitespace=False) + '\n' return _fmt def _load_group_config(self): """ Attempts to load the host group specified on the command line. Host groups are defined via JSON files, typically saved under /etc/sos/groups.d/, although users can specify a full filepath on the commandline to point to one existing anywhere on the system Host groups define a list of nodes and/or regexes and optionally the primary and cluster-type options. """ grp = self.opts.group paths = [ grp, os.path.join(Path.home(), f'.config/sos/groups.d/{grp}'), os.path.join(COLLECTOR_CONFIG_DIR, grp) ] fname = None for path in paths: if os.path.exists(path): fname = path break if fname is None: raise OSError(f"no group definition for {grp}") self.log_debug(f"Loading host group {fname}") with open(fname, 'r', encoding='utf-8') as hf: _group = json.load(hf) for key in ['primary', 'cluster_type']: if _group[key]: self.log_debug(f"Setting option '{key}' to '{_group[key]}'" "per host group") setattr(self.opts, key, _group[key]) if _group['nodes']: self.log_debug(f"Adding {_group['nodes']} to node list") self.opts.nodes.extend(_group['nodes']) def write_host_group(self): """ Saves the results of this run of sos collect to a host group file on the system so it can be used later on. The host group will save the options primary, cluster_type, and nodes as determined by sos collect prior to execution of sos reports. """ cfg = { 'name': self.opts.save_group, 'primary': self.opts.primary, 'cluster_type': self.cluster.cluster_type[0], 'nodes': list(self.node_list) } if os.getuid() != 0: group_path = os.path.join(Path.home(), '.config/sos/groups.d') # create the subdir within the user's home directory os.makedirs(group_path, exist_ok=True) else: group_path = COLLECTOR_CONFIG_DIR fname = os.path.join(group_path, cfg['name']) with open(fname, 'w', encoding='utf-8') as hf: json.dump(cfg, hf) os.chmod(fname, 0o600) return fname def prep(self): self.policy.set_commons(self.commons) if (not self.opts.password and not self.opts.password_per_node): self.log_debug('password not specified, assuming SSH keys') msg = ('sos collect ASSUMES that SSH keys are installed on all ' 'nodes unless the --password option is provided.\n') self.ui_log.info(self._fmt_msg(msg)) try: if ((self.opts.password or (self.opts.password_per_node and self.opts.primary)) and not self.opts.batch): self.log_debug('password specified, not using SSH keys') msg = ('Provide the SSH password for user ' f'{self.opts.ssh_user}: ') self.opts.password = getpass(prompt=msg) if ((self.commons['need_sudo'] and not self.opts.nopasswd_sudo) and not self.opts.batch): if not self.opts.password and not self.opts.password_per_node: self.log_debug('non-root user specified, will request ' 'sudo password') msg = ('A non-root user has been provided. Provide sudo ' f'password for {self.opts.ssh_user} on remote ' 'nodes: ') self.opts.sudo_pw = getpass(prompt=msg) else: if not self.opts.nopasswd_sudo: self.opts.sudo_pw = self.opts.password except KeyboardInterrupt: self.exit("\nExiting on user cancel\n", 130) if self.opts.become_root: if not self.opts.ssh_user == 'root': if self.opts.batch: msg = ("Cannot become root without obtaining root " "password. Do not use --batch if you need " "to become root remotely.") self.exit(msg, 1) self.log_debug('non-root user asking to become root remotely') msg = (f'User {self.opts.ssh_user} will attempt to become ' 'root. Provide root password: ') self.opts.root_password = getpass(prompt=msg) self.commons['need_sudo'] = False else: self.log_info('Option to become root but ssh user is root.' ' Ignoring request to change user on node') self.opts.become_root = False if self.opts.group: try: self._load_group_config() except Exception as err: msg = (f"Could not load specified group {self.opts.group}: " f"{err}") self.exit(msg, 1) try: self.policy.pre_work() except KeyboardInterrupt: self.exit("Exiting on user cancel\n", 130) if self.opts.primary: self.connect_to_primary() self.opts.no_local = True else: try: can_run_local = True local_sudo = None skip_local_msg = ( "Local sos report generation forcibly skipped due " "to lack of root privileges.\nEither use --nopasswd-sudo, " "run as root, or do not use --batch so that you will be " "prompted for a password\n" ) if (not self.opts.no_local and (os.getuid() != 0 and not self.opts.nopasswd_sudo)): if not self.opts.batch: msg = ("Enter local sudo password to generate local " "sos report: ") local_sudo = getpass(msg) if local_sudo == '': self.ui_log.info(skip_local_msg) can_run_local = False self.opts.no_local = True local_sudo = None else: self.ui_log.info(skip_local_msg) can_run_local = False self.opts.no_local = True self.primary = SosNode('localhost', self.commons, local_sudo=local_sudo, load_facts=can_run_local) except Exception as err: self.log_debug("Unable to determine local installation: " f"{err}") self.exit('Unable to determine local installation. Use the ' '--no-local option if localhost should not be ' 'included.\nAborting...\n', 1) self.collect_md.add_field('primary', self.primary.address) self.collect_md.add_section('nodes') self.collect_md.nodes.add_section(self.primary.address) self.primary.set_node_manifest(getattr(self.collect_md.nodes, self.primary.address)) if self.opts.cluster_type: if self.opts.cluster_type == 'none': self.cluster = self.clusters['jbon'] else: self.cluster = self.clusters[self.opts.cluster_type] self.cluster_type = self.opts.cluster_type self.cluster.primary = self.primary else: self.determine_cluster() if self.cluster is None and not self.opts.nodes: msg = ('Cluster type could not be determined and no nodes provided' '\nAborting...') self.exit(msg, 1) elif self.cluster is None and self.opts.nodes: self.log_info("Cluster type could not be determined, but --nodes " "is provided. Attempting to continue using JBON " "cluster type and the node list") self.cluster = self.clusters['jbon'] self.cluster_type = 'none' self.collect_md.add_field('cluster_type', self.cluster_type) if self.cluster: self.primary.cluster = self.cluster if self.opts.transport == 'auto': self.opts.transport = self.cluster.set_transport_type() self.cluster.setup() if self.cluster.cluster_ssh_key: if not self.opts.ssh_key: self.log_debug( f"Updating SSH key to {self.cluster.cluster_ssh_key} " "per cluster") self.opts.ssh_key = self.cluster.cluster_ssh_key self.get_nodes() if self.opts.save_group: gname = self.opts.save_group try: fname = self.write_host_group() self.log_info(f"Wrote group '{gname}' to {fname}") except Exception as err: self.log_error(f"Could not save group {gname}: {err}") def display_nodes(self): """Prints a list of nodes to collect from, if available. If no nodes are discovered or provided, abort. """ self.ui_log.info('') if not self.node_list and not self.primary.connected: self.exit('No nodes were detected, or nodes do not have sos ' 'installed.\nAborting...', 1) self.ui_log.info('The following is a list of nodes to collect from:') if self.primary.connected and self.primary.hostname is not None: if not ((self.primary.local and self.opts.no_local) or self.cluster.strict_node_list): self.ui_log.info( f"\t{self.primary.hostname:<{self.commons['hostlen']}}" ) for node in sorted(self.node_list): self.ui_log.info(f"\t{node:<{self.commons['hostlen']}}") self.ui_log.info('') if not self.opts.batch: try: input("\nPress ENTER to continue with these nodes, or press " "CTRL-C to quit\n") self.ui_log.info("") except KeyboardInterrupt: self.exit("Exiting on user cancel", 130) except Exception as e: self.exit(repr(e), 1) def configure_sos_cmd(self): """Configures the sos report command that is run on the nodes""" sos_cmd = 'sosreport --batch ' sos_options = {} if self.opts.case_id: sos_options['case-id'] = quote(self.opts.case_id) if self.opts.alloptions: sos_options['alloptions'] = '' if self.opts.all_logs: sos_options['all-logs'] = '' if self.opts.verify: sos_options['verify'] = '' if self.opts.log_size: sos_options['log-size'] = quote(str(self.opts.log_size)) if self.opts.sysroot: sos_options['sysroot'] = quote(self.opts.sysroot) if self.opts.chroot: sos_options['chroot'] = quote(self.opts.chroot) if self.opts.compression_type != 'auto': sos_options['compression-type'] = quote(self.opts.compression_type) for k, v in sos_options.items(): sos_cmd += f"--{k} {v} " sos_cmd = sos_cmd.rstrip() self.log_debug(f"Initial sos cmd set to {sos_cmd}") self.commons['sos_cmd'] = 'sosreport --batch ' self.commons['sos_options'] = sos_options self.collect_md.add_field('initial_sos_cmd', sos_cmd) def connect_to_primary(self): """If run with --primary, we will run cluster checks again that instead of the localhost. """ try: self.primary = SosNode(self.opts.primary, self.commons) self.ui_log.info(f'Connected to {self.opts.primary}, determining ' 'cluster type...') except Exception as e: self.log_debug(f'Failed to connect to primary node: {e}') self.exit('Could not connect to primary node. Aborting...', 1) def determine_cluster(self): """This sets the cluster type and loads that cluster's cluster. If no cluster type is matched and no list of nodes is provided by the user, then we abort. 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 """ checks = list(self.clusters.values()) for cluster in self.clusters.values(): checks.remove(cluster) cluster.primary = self.primary if cluster.check_enabled(): cname = cluster.__class__.__name__ self.log_debug(f"Installation matches {cname}, checking for " "layered profiles") for remaining in checks: if issubclass(remaining.__class__, cluster.__class__): rname = remaining.__class__.__name__ self.log_debug(f"Layered profile {rname} found. " "Checking installation") remaining.primary = self.primary if remaining.check_enabled(): self.log_debug("Installation matches both layered " f"profile {rname} and base profile " f"{cname}, setting cluster type to " "layered profile") cluster = remaining break self.cluster = cluster self.cluster_type = cluster.name() self.commons['cluster'] = self.cluster self.ui_log.info( f'Cluster type set to {self.cluster_type}') break def get_nodes_from_cluster(self): """Collects the list of nodes from the determined cluster cluster""" if self.cluster_type: nodes = self.cluster._get_nodes() self.log_debug(f'Node list: {nodes}') return nodes return [] def reduce_node_list(self): """Reduce duplicate entries of the localhost and/or primary node if applicable""" if (self.hostname in self.node_list and self.opts.no_local): self.node_list.remove(self.hostname) if not self.cluster.strict_node_list: for i in self.ip_addrs: if i in self.node_list: self.node_list.remove(i) # remove the primary node from the list, since we already have # an open session to it. if self.primary is not None and not self.cluster.strict_node_list: for n in self.node_list: if n in (self.primary.hostname, self.opts.primary): self.node_list.remove(n) self.node_list = list(set(n for n in self.node_list if n)) self.log_debug(f'Node list reduced to {self.node_list}') self.collect_md.add_list('node_list', self.node_list) def compare_node_to_regex(self, node): """Compares a discovered node name to a provided list of nodes from the user. If there is not a match, the node is removed from the list""" for regex in self.opts.nodes: try: regex = fnmatch.translate(regex) if re.match(regex, node): return True except re.error as err: msg = 'Error comparing %s to provided node regex %s: %s' self.log_debug(msg % (node, regex, err)) return False def get_nodes(self): """ Sets the list of nodes to collect sos reports from """ if not self.primary and not self.cluster: msg = ('Could not determine a cluster type and no list of ' 'nodes or primary node was provided.\nAborting...' ) self.exit(msg, 1) try: nodes = self.get_nodes_from_cluster() if self.opts.nodes: for node in nodes: if self.compare_node_to_regex(node): self.node_list.append(node) else: self.node_list = nodes except Exception as e: self.log_debug(f"Error parsing node list: {e}") self.log_debug('Setting node list to --nodes option') self.node_list = self.opts.nodes for node in self.node_list: if any(i in node for i in ('*', '\\', '?', '(', ')', '/')): self.node_list.remove(node) # force add any non-regex node strings from nodes option if self.opts.nodes: for node in self.opts.nodes: if any(i in node for i in '*\\?()/[]'): continue if node not in self.node_list: self.log_debug(f"Force adding {node} to node list") self.node_list.append(node) if not self.primary: host = self.hostname.split('.')[0] # trust the local hostname before the node report from cluster for node in self.node_list: if host == node.split('.')[0]: self.node_list.remove(node) if not self.cluster.strict_node_list: self.node_list.append(self.hostname) self.reduce_node_list() try: _node_max = len(max(self.node_list, key=len)) self.commons['hostlen'] = max(_node_max, self.commons['hostlen']) except (TypeError, ValueError) as err: self.log_debug(f"Could not set UI spacing: {err}") def _connect_to_node(self, node): """Try to connect to the node, and if we can add to the client list to run sos report on Positional arguments node - a tuple specifying (address, password). If no password, set to None """ try: client = SosNode(node[0], self.commons, password=node[1]) client.set_cluster(self.cluster) if client.connected: self.client_list.append(client) self.collect_md.nodes.add_section(node[0]) client.set_node_manifest(getattr(self.collect_md.nodes, node[0])) else: client.disconnect() except Exception: # all exception logging is handled within SoSNode pass def intro(self): """Print the intro message and prompts for a case ID if one is not provided on the command line """ disclaimer = """\ This utility is used to collect sos reports from multiple \ nodes simultaneously. Remote connections are made and/or maintained \ to those nodes via well-known transport protocols such as SSH. An archive of sos report tarballs collected from the nodes will be \ generated in %s and may be provided to an appropriate support representative. The generated archive may contain data considered sensitive \ and its content should be reviewed by the originating \ organization before being passed to any third party. No configuration changes will be made to the system running \ this utility or remote systems that it connects to. """ self.ui_log.info(f"\nsos collect (version {__version__})\n") intro_msg = self._fmt_msg(disclaimer % self.tmpdir) self.ui_log.info(intro_msg) prompt = "\nPress ENTER to continue, or CTRL-C to quit\n" if not self.opts.batch: try: input(prompt) self.ui_log.info("") except KeyboardInterrupt: self.exit("Exiting on user cancel", 130) except Exception as e: self.exit(e, 1) def execute(self): if self.opts.list_options: self.list_options() self.exit() self.intro() self.configure_sos_cmd() self.prep() self.display_nodes() self.archive_name = self._get_archive_name() self.setup_archive(name=self.archive_name) self.archive_path = self.archive.get_archive_path() self.archive.makedirs('sos_logs', 0o755) self.collect() self.exit() def collect(self): """ For each node, start a collection thread and then tar all collected sos reports """ filters = set([self.primary.address, self.primary.hostname]) # add primary if: # - we are connected to it and # - its hostname is in node_list, or # - we dont forcibly remove local host from collection # (i.e. strict_node_list=False) if self.primary.connected and \ (filters.intersection(set(self.node_list)) or not self.cluster.strict_node_list): self.client_list.append(self.primary) self.ui_log.info("\nConnecting to nodes...") nodes = [(n, None) for n in self.node_list if n not in filters] if self.opts.password_per_node: _nodes = [] for node in nodes: msg = (f"Please enter the password for {self.opts.ssh_user}@" f"{node[0]}: ") node_pwd = getpass(msg) _nodes.append((node[0], node_pwd)) nodes = _nodes try: pool = ThreadPoolExecutor(self.opts.jobs) pool.map(self._connect_to_node, nodes, chunksize=1) pool.shutdown(wait=True) if (self.opts.no_local and self.client_list[0].address == 'localhost'): self.client_list.pop(0) self.report_num = len(self.client_list) if self.report_num == 0: self.exit("No nodes connected. Aborting...", 1) elif self.report_num == 1: if self.client_list[0].address == 'localhost': self.exit( "Collection would only gather from localhost due to " "failure to either enumerate or connect to cluster " "nodes. Assuming single collection from localhost is " "not desired.\n" "Aborting...", 1 ) self.ui_log.info("\nBeginning collection of sos reports from " f"{self.report_num} nodes, collecting a maximum " f"of {self.opts.jobs} concurrently\n") npool = ThreadPoolExecutor(self.opts.jobs) npool.map(self._finalize_sos_cmd, self.client_list, chunksize=1) npool.shutdown(wait=True) pool = ThreadPoolExecutor(self.opts.jobs) pool.map(self._collect, self.client_list, chunksize=1) pool.shutdown(wait=True) except KeyboardInterrupt: self.exit("Exiting on user cancel\n", 130, force=True) except Exception as err: msg = f'Could not connect to nodes: {err}' self.exit(msg, 1, force=True) if hasattr(self.cluster, 'run_extra_cmd'): self.ui_log.info('Collecting additional data from primary node...') files = self.cluster._run_extra_cmd() if files: self.primary.collect_extra_cmd(files) msg = '\nSuccessfully captured %s of %s sos reports' self.log_info(msg % (self.retrieved, self.report_num)) self.close_all_connections() if self.retrieved > 0: self.arc_name = self.create_cluster_archive() else: msg = 'No sos reports were collected, nothing to archive...' self.exit(msg, 1) if (self.opts.upload and self.policy.get_upload_url()) or \ self.opts.upload_s3_endpoint: try: self.policy.upload_archive(self.arc_name) self.ui_log.info("Uploaded archive successfully") except Exception as err: self.ui_log.error(f"Upload attempt failed: {err}") def _finalize_sos_cmd(self, client): """Calls finalize_sos_cmd() on each node so that we have the final command before we thread out the actual execution of sos """ try: client.finalize_sos_cmd() except Exception as err: self.log_error("Could not finalize sos command for " f"{client.address}: {err}") def _collect(self, client): """Runs sos report on each node""" try: if not client.local: client.sosreport() else: if not self.opts.no_local: client.sosreport() if client.retrieved: self.retrieved += 1 except Exception as err: self.log_error(f"Error running sos report: {err}") def close_all_connections(self): """Close all sessions for nodes""" for client in self.client_list: if client.connected: self.log_debug(f'Closing connection to {client.address}') client.disconnect() def create_cluster_archive(self): """Calls for creation of tar archive then cleans up the temporary files created by sos collect""" map_file = None arc_paths = [] for host in self.client_list: for fname in host.file_list: arc_paths.append(fname) do_clean = False if self.opts.clean: hook_commons = { 'policy': self.policy, 'tmpdir': self.tmpdir, 'sys_tmp': self.sys_tmp, 'options': self.opts, 'manifest': self.manifest } try: self.ui_log.info('') cleaner = SoSCleaner(in_place=True, hook_commons=hook_commons) cleaner.set_target_path(self.tmpdir) map_file, arc_paths = cleaner.execute() do_clean = True except Exception as err: self.ui_log.error(f"ERROR: unable to obfuscate reports: {err}") try: self.log_info('Creating archive of sos reports...') for fname in arc_paths: dest = fname.split('/')[-1] if do_clean: dest = cleaner.obfuscate_string(dest) name = os.path.join(self.tmpdir, fname) self.archive.add_file(name, dest=dest) if map_file: # regenerate the checksum for the obfuscated archive checksum = cleaner.get_new_checksum(fname) if checksum: name = os.path.join('checksums', fname.split('/')[-1]) name += '.sha256' self.archive.add_string(checksum, name) self.archive.add_file(self.sos_log_file, dest=os.path.join('sos_logs', 'sos.log')) self.archive.add_file(self.sos_ui_log_file, dest=os.path.join('sos_logs', 'ui.log')) if self.manifest is not None: self.archive.add_final_manifest_data( self.opts.compression_type ) self._obfuscate_upload_passwords() if do_clean: _dir = os.path.join(self.tmpdir, self.archive._name) cleaner.obfuscate_file( os.path.join(_dir, 'sos_logs', 'sos.log'), short_name='sos.log' ) cleaner.obfuscate_file( os.path.join(_dir, 'sos_logs', 'ui.log'), short_name='ui.log' ) cleaner.obfuscate_file( os.path.join(_dir, 'sos_reports', 'manifest.json'), short_name='manifest.json' ) arc_name = self.archive.finalize(self.opts.compression_type) final_name = os.path.join(self.sys_tmp, os.path.basename(arc_name)) if do_clean: final_name = cleaner.obfuscate_string( final_name.replace('.tar', '-obfuscated.tar') ) os.rename(arc_name, final_name) if map_file: # rename the map file to match the collector archive name, not # the temp dir it was constructed in map_name = cleaner.obfuscate_string( os.path.join(self.sys_tmp, f"{self.archive_name}_private_map") ) os.rename(map_file, map_name) self.ui_log.info("A mapping of obfuscated elements is " f"available at\n\t{map_name}") self.soslog.info(f'Archive created as {final_name}') self.ui_log.info('\nThe following archive has been created. ' 'Please provide it to your support team.') self.ui_log.info(f'\t{final_name}\n') return final_name except Exception as err: msg = (f"Could not finalize archive: {err}\n\nData may still be " f"available uncompressed at {self.archive_path}") self.exit(msg, 2) # Never gets here. This is to fix "inconsistent-return-statements return "Archive error" sos-4.8.0/sos/collector/exceptions.py0000664000175000017500000001022714660147624016316 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. class InvalidPasswordException(Exception): """Raised when the provided password is rejected by the remote host""" def __init__(self): message = 'Invalid password provided' super().__init__(message) class TimeoutPasswordAuthException(Exception): """Raised when a timeout is hit waiting for an auth reply using a password """ def __init__(self): message = 'Timeout hit while waiting for password validation' super().__init__(message) class PasswordRequestException(Exception): """Raised when the remote host requests a password that was not anticipated """ def __init__(self): message = 'Host requested password, but none provided' super().__init__(message) class AuthPermissionDeniedException(Exception): """Raised when authentication attempts return a permission error""" def __init__(self): message = 'Permission denied while trying to authenticate' super().__init__(message) class ConnectionException(Exception): """Raised when an attempt to connect fails""" def __init__(self, address='', port=''): message = (f"Could not connect to host {address} on specified port " f"{port}") super().__init__(message) class CommandTimeoutException(Exception): """Raised when a timeout expires""" def __init__(self, command=None): message = 'Timeout expired' if command: message += f" executing {command}" super().__init__(message) class ConnectionTimeoutException(Exception): """Raised when a timeout expires while trying to connect to the host""" def __init__(self): message = 'Timeout expires while trying to connect' super().__init__(message) class ControlSocketMissingException(Exception): """Raised when the SSH control socket is missing""" def __init__(self, path=''): message = f"SSH control socket {path} does not exist" super().__init__(message) class ControlPersistUnsupportedException(Exception): """Raised when SSH ControlPersist is unsupported locally""" def __init__(self): message = 'ControlPersist unsupported by local SSH installation' super().__init__(message) class UnsupportedHostException(Exception): """Raised when the host type is unsupported or undetermined""" def __init__(self): message = 'Host did not match any supported distributions' super().__init__(message) class InvalidTransportException(Exception): """Raised when a transport is requested but it does not exist or is not supported locally""" def __init__(self, transport=None): message = ("Connection failed: unknown or unsupported transport " f"{transport if transport else ''}") super().__init__(message) class SaltStackMasterUnsupportedException(Exception): """Raised when SaltStack Master is unsupported locally""" def __init__(self): message = 'Master unsupported by local SaltStack installation' super().__init__(message) class JujuNotInstalledException(Exception): """Raised when juju is not installed locally""" def __init__(self): message = ( 'Juju is not installed, ' 'please ensure you have installed juju.' ) super().__init__(message) __all__ = [ 'AuthPermissionDeniedException', 'CommandTimeoutException', 'ConnectionException', 'ConnectionTimeoutException', 'ControlPersistUnsupportedException', 'ControlSocketMissingException', 'InvalidPasswordException', 'PasswordRequestException', 'SaltStackMasterUnsupportedException', 'TimeoutPasswordAuthException', 'UnsupportedHostException', 'InvalidTransportException', 'JujuNotInstalledException' ] sos-4.8.0/sos/collector/sosnode.py0000664000175000017500000011162514660147624015613 0ustar arifarif# Copyright Red Hat 2020, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # pylint: disable=too-many-branches import fnmatch import inspect import logging import os import re from shlex import quote from sos.policies import load from sos.policies.init_systems import InitSystem from sos.collector.transports.juju import JujuSSH from sos.collector.transports.control_persist import SSHControlPersist from sos.collector.transports.local import LocalTransport from sos.collector.transports.oc import OCTransport from sos.collector.transports.saltstack import SaltStackMaster from sos.collector.exceptions import (CommandTimeoutException, ConnectionException, UnsupportedHostException, InvalidTransportException) from sos.utilities import sos_parse_version TRANSPORTS = { 'local': LocalTransport, 'control_persist': SSHControlPersist, 'oc': OCTransport, 'saltstack': SaltStackMaster, 'juju': JujuSSH, } class SosNode(): def __init__(self, address, commons, password=None, local_sudo=None, load_facts=True): self.address = address.strip() self.commons = commons self.opts = commons['cmdlineopts'] self._assign_config_opts() self.tmpdir = commons['tmpdir'] self.hostlen = commons['hostlen'] self.need_sudo = commons['need_sudo'] self.sos_options = commons['sos_options'] self.local = False self.host = None self.cluster = None self.hostname = None self.sos_env_vars = {} self._env_vars = {} self._password = password or self.opts.password if not self.opts.nopasswd_sudo and not self.opts.sudo_pw: self.opts.sudo_pw = self._password # override local sudo from any other source if local_sudo: self.opts.sudo_pw = local_sudo self.sos_path = None self.retrieved = False self.hash_retrieved = False self.file_list = [] self.sos_info = { 'version': None, 'enabled': [], 'disabled': [], 'options': [], 'presets': [], 'sos_cmd': commons['sos_cmd'] } self.sos_bin = 'sosreport' self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') self._transport = self._load_remote_transport(commons) # Overwrite need_sudo if transports default_user # is set and is not root. if self._transport.default_user: self.need_sudo = self._transport.default_user != 'root' try: self._transport.connect(self._password) except Exception as err: self.log_error(f'Unable to open remote session: {err}') raise # load the host policy now, even if we don't want to load further # host information. This is necessary if we're running locally on the # cluster primary but do not want a local report as we still need to do # package checks in that instance self.host = self.determine_host_policy() self.hostname = self._transport.hostname if self.local and self.opts.no_local: load_facts = False if self.connected and load_facts: if not self.host: self._transport.disconnect() return None if self.local: if self.check_in_container(): self.host.containerized = False if self.host.containerized: self.create_sos_container() self._load_sos_info() return None @property def connected(self): if self._transport: return self._transport.connected # if no transport, we're running locally return True def disconnect(self): """Wrapper to close the remote session via our transport agent """ self._transport.disconnect() def _load_remote_transport(self, commons): """Determine the type of remote transport to load for this node, then return an instantiated instance of that transport """ if self.address in ['localhost', '127.0.0.1']: self.local = True return LocalTransport(self.address, commons) if self.opts.transport in TRANSPORTS.keys(): return TRANSPORTS[self.opts.transport](self.address, commons) if self.opts.transport != 'auto': self.log_error( "Connection failed: unknown or unsupported transport " f"{self.opts.transport}" ) raise InvalidTransportException(self.opts.transport) return SSHControlPersist(self.address, commons) def _fmt_msg(self, msg): return f"{self._hostname:<{self.hostlen + 1}} : {msg}" @property def env_vars(self): if not self._env_vars: if self.local: self._env_vars = os.environ.copy() else: ret = self.run_command("env --null") if ret['status'] == 0: for ln in ret['output'].split('\x00'): if not ln: continue _val = ln.split('=') self._env_vars[_val[0]] = _val[1] return self._env_vars def set_node_manifest(self, manifest): """Set the manifest section that this node will write to """ self.manifest = manifest self.manifest.add_field('hostname', self._hostname) self.manifest.add_field('policy', self.host.distro) self.manifest.add_field('sos_version', self.sos_info['version']) self.manifest.add_field('final_sos_command', '') self.manifest.add_field('transport', self._transport.name) 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 """ if self.host.containerized: cmd = self.host.create_sos_container( image=self.opts.image, auth=self.get_container_auth(), force_pull=self.opts.force_pull_image ) res = self.run_command(cmd, need_root=True) if res['status'] in [0, 125]: if res['status'] == 125: if 'unable to retrieve auth token' in res['output']: self.log_error( "Could not pull image. Provide either a username " "and password or authfile" ) raise Exception if 'unknown: Not found' in res['output']: self.log_error('Specified image not found on registry') raise Exception # 'name exists' with code 125 means the container was # created successfully, so ignore it. # initial creations leads to an exited container, restarting it # here will keep it alive for us to exec through ret = self.run_command(self.host.restart_sos_container(), need_root=True) if ret['status'] == 0: self.log_info("Temporary container " f"{self.host.sos_container_name} created") return True self.log_error("Could not start container after create: " f"{ret['output']}") raise Exception self.log_error("Could not create container on host: " f"{res['output']}") raise Exception return False def get_container_auth(self): """Determine what the auth string should be to pull the image used to deploy our temporary container """ if self.opts.registry_user: return self.host.runtimes['default'].fmt_registry_credentials( self.opts.registry_user, self.opts.registry_password ) return self.host.runtimes['default'].fmt_registry_authfile( self.opts.registry_authfile or self.host.container_authfile ) def file_exists(self, fname, need_root=False): """Checks for the presence of fname on the remote node""" try: res = self.run_command(f"stat {fname}", need_root=need_root) return res['status'] == 0 except Exception: return False @property def _hostname(self): if self.hostname and 'localhost' not in self.hostname: return self.hostname return self.address def _sanitize_log_msg(self, msg): """Attempts to obfuscate sensitive information in log messages such as passwords""" reg = r'(?P(pass|key|secret|PASS|KEY|SECRET).*?=)(?P.*?\s)' return re.sub(reg, r'\g****** ', msg) def ui_msg(self, msg): """Format a ui message that includes host name and formatting""" self.ui_log.info(self._fmt_msg(msg)) def log_info(self, msg): """Used to print and log info messages""" caller = inspect.stack()[1][3] lmsg = f'[{self._hostname}:{caller}] {msg}' self.soslog.info(lmsg) def log_error(self, msg): """Used to print and log error messages""" caller = inspect.stack()[1][3] lmsg = f'[{self._hostname}:{caller}] {msg}' self.soslog.error(lmsg) def log_debug(self, msg): """Used to print and log debug messages""" msg = self._sanitize_log_msg(msg) caller = inspect.stack()[1][3] msg = f'[{self._hostname}:{caller}] {msg}' self.soslog.debug(msg) def _format_cmd(self, cmd): """If we need to provide a sudo or root password to a command, then here we prefix the command with the correct bits """ if self.opts.become_root: return f"su -c {quote(cmd)}" if self.need_sudo: return f"sudo -S {cmd}" return cmd def _load_sos_info(self): """Queries the node for information about the installed version of sos """ ver = None rel = None if self.host.container_version_command is None: pkg = self.host.package_manager.pkg_version(self.host.sos_pkg_name) if pkg is not None: ver = '.'.join(pkg['version']) if pkg['release']: rel = pkg['release'] else: # use the containerized policy's command pkgs = self.run_command(self.host.container_version_command, use_container=True, need_root=True) if pkgs['status'] == 0: _, ver, rel = pkgs['output'].strip().split('-') if ver: if len(ver.split('.')) == 2: # safeguard against maintenance releases throwing off the # comparison by parse_version ver += '.0' try: ver += f'-{rel.split(".")[0]}' except Exception as err: self.log_debug(f"Unable to fully parse sos release: {err}") self.sos_info['version'] = ver if self.sos_info['version']: self.log_info(f'sos version is {self.sos_info["version"]}') else: if not self.address == self.opts.primary: # in the case where the 'primary' enumerates nodes but is not # intended for collection (bastions), don't worry about sos not # being present self.log_error('sos is not installed on this node') self.connected = False return False # sos-4.0 changes the binary if self.check_sos_version('4.0'): self.sos_bin = 'sos report' cmd = f"{self.sos_bin} -l" sosinfo = self.run_command(cmd, use_container=True, need_root=True) if sosinfo['status'] == 0: self._load_sos_plugins(sosinfo['output']) if self.check_sos_version('3.6'): self._load_sos_presets() return None def _load_sos_presets(self): cmd = f'{self.sos_bin} --list-presets' res = self.run_command(cmd, use_container=True, need_root=True) if res['status'] == 0: for line in res['output'].splitlines(): if line.strip().startswith('name:'): pname = line.split('name:')[1].strip() self.sos_info['presets'].append(pname) def _load_sos_plugins(self, sosinfo): ENABLED = 'The following plugins are currently enabled:' DISABLED = 'The following plugins are currently disabled:' ALL_OPTIONS = 'The following options are available for ALL plugins:' OPTIONS = 'The following plugin options are available:' PROFILES = 'Profiles:' enablereg = ENABLED + '(.*?)' + DISABLED disreg = DISABLED + '(.*?)' + ALL_OPTIONS optreg = OPTIONS + '(.*?)' + PROFILES proreg = PROFILES + '(.*?)' + '\n\n' self.sos_info['enabled'] = self._regex_sos_help(enablereg, sosinfo) self.sos_info['disabled'] = self._regex_sos_help(disreg, sosinfo) self.sos_info['options'] = self._regex_sos_help(optreg, sosinfo) self.sos_info['profiles'] = self._regex_sos_help(proreg, sosinfo, True) def _regex_sos_help(self, regex, sosinfo, is_list=False): res = [] for result in re.findall(regex, sosinfo, re.S): for line in result.splitlines(): if not is_list: try: res.append(line.split()[0]) except Exception as err: self.log_debug(f"Error parsing sos help: {err}") else: r = line.split(',') res.extend(p.strip() for p in r if p.strip()) return res def read_file(self, to_read): """Reads the specified file and returns the contents""" try: self.log_info(f"Reading file {to_read}") return self._transport.read_file(to_read) except Exception as err: self.log_error(f"Exception while reading {to_read}: {err}") return '' def determine_host_policy(self): """Attempts to identify the host installation against supported distributions """ if self.local: self.log_info( f"using local policy {self.commons['policy'].distro}") return self.commons['policy'] host = load(cache={}, sysroot=self.opts.sysroot, init=InitSystem(), probe_runtime=True, remote_exec=self._transport.run_command, remote_check=self.read_file('/etc/os-release')) if host: self.log_info(f"loaded policy {host.distro} for host") return host self.log_error('Unable to determine host installation. Ignoring node') raise UnsupportedHostException def check_sos_version(self, ver): """Checks to see if the sos installation on the node is AT LEAST the given ver. This means that if the installed version is greater than ver, this will still return True :param ver: Version number we are trying to verify is installed :type ver: ``str`` :returns: True if installed version is at least ``ver``, else False :rtype: ``bool`` """ try: _node_ver = self.sos_info['version'] return sos_parse_version(_node_ver) >= sos_parse_version(ver) except Exception as err: self.log_error(f"Error checking sos version: {err}") return False def is_installed(self, pkg): """Checks if a given package is installed on the node""" if not self.host: return False return self.host.package_manager.pkg_by_name(pkg) is not None def run_command(self, cmd, timeout=180, use_shell='auto', need_root=False, use_container=False, env=None): """Runs a given cmd, either via the SSH session or locally :param cmd: The full command to be run :type cmd: ``str`` :param timeout: Time in seconds to wait for `cmd` to complete :type timeout: ``int`` :param use_shell: If a shell is needed to run `cmd`, set to True :type use_shell: ``bool`` or ``auto`` for transport-determined :param use_container: Run this command in a container *IF* the host is a containerized host :type use_container: ``bool`` :param env: Pass environment variables to set for this `cmd` :type env: ``dict`` """ if not self.connected and not self.local: self.log_debug('Node is disconnected, attempting to reconnect') try: reconnected = self._transport.reconnect(self._password) if not reconnected: self.log_debug('Failed to reconnect to node') raise ConnectionException except Exception as err: self.log_debug(f"Error while trying to reconnect: {err}") raise if use_container and self.host.containerized: cmd = self.host.format_container_command(cmd) if need_root: cmd = self._format_cmd(cmd) if env: _cmd_env = self.env_vars env = _cmd_env.update(env) return self._transport.run_command(cmd, timeout, need_root, env, use_shell) def sosreport(self): """Run an sos report on the node, then collect it""" try: path = self.execute_sos_command() if path: self.finalize_sos_path(path) else: self.log_error('Unable to determine path of sos archive') if self.sos_path: self.retrieved = self.retrieve_sosreport() except Exception as err: self.log_error(f"Error during sos execution: {err}") self.cleanup() def _preset_exists(self, preset): """Verifies if the given preset exists on the node""" return preset in self.sos_info['presets'] def _plugin_exists(self, plugin): """Verifies if the given plugin exists on the node""" return any(plugin in s for s in [self.sos_info['enabled'], self.sos_info['disabled']]) def _check_enabled(self, plugin): """Checks to see if the plugin is default enabled on node""" return plugin in self.sos_info['enabled'] def _check_disabled(self, plugin): """Checks to see if the plugin is default disabled on node""" return plugin in self.sos_info['disabled'] def _plugin_option_exists(self, opt): """Attempts to verify that the given option is available on the node. Note that we only get available options for enabled plugins, so if a plugin has been force-enabled we cannot validate if the plugin option is correct or not""" plug = opt.split('.')[0] if not self._plugin_exists(plug): return False if (self._check_disabled(plug) and plug not in self.opts.enable_plugins): return False if self._check_enabled(plug): return opt in self.sos_info['options'] # plugin exists, but is normally disabled. Assume user knows option is # valid when enabling the plugin return True def _fmt_sos_opt_list(self, opts): """Returns a comma delimited list for sos plugins that are confirmed to exist on the node""" return ','.join(o for o in opts if self._plugin_exists(o)) def set_cluster(self, cluster): """Expose the node to the cluster profile determined for the environment """ self.cluster = cluster def update_cmd_from_cluster(self): """This is used to modify the sos report command run on the nodes. By default, sos report is run without any options, using this will allow the profile to specify what plugins to run or not and what options to use. This will NOT override user supplied options. """ if self.cluster.sos_preset: if not self.preset: self.preset = self.cluster.sos_preset else: self.log_info('Cluster specified preset ' f'{self.cluster.sos_preset} but user has also ' 'defined a preset. Using user specification.') if self.cluster.sos_plugins: for plug in self.cluster.sos_plugins: if plug not in self.enable_plugins: self.enable_plugins.append(plug) if self.cluster.sos_options: for opt in self.cluster.sos_options: # take the user specification over any cluster defaults if opt not in self.sos_options: self.sos_options[opt] = self.cluster.sos_options[opt] if self.cluster.sos_plugin_options: for opt in self.cluster.sos_plugin_options: if not any(opt in o for o in self.plugopts): option = f'{opt}={self.cluster.sos_plugin_options[opt]}' self.plugopts.append(option) # set primary-only options if self.cluster.check_node_is_primary(self): with self.cluster.lock: self.cluster.set_primary_options(self) else: with self.cluster.lock: self.cluster.set_node_options(self) def _assign_config_opts(self): """From the global opts configuration, assign those values locally to this node so that they may be acted on individually. """ # assign these to new, private copies self.only_plugins = list(self.opts.only_plugins) self.skip_plugins = list(self.opts.skip_plugins) self.enable_plugins = list(self.opts.enable_plugins) self.plugopts = list(self.opts.plugopts) self.preset = list(self.opts.preset) def finalize_sos_cmd(self): """Use host facts and compare to the cluster type to modify the sos command if needed""" sos_cmd = self.sos_info['sos_cmd'] label = self.determine_sos_label() if label: sos_cmd = f'{sos_cmd} {quote(label)} ' sos_opts = [] # sos-3.6 added --threads if self.check_sos_version('3.6'): # 4 threads is the project's default if self.opts.threads != 4: sos_opts.append(f'--threads={quote(str(self.opts.threads))}') # sos-3.7 added options if self.check_sos_version('3.7'): if self.opts.plugin_timeout: sos_opts.append( f'--plugin-timeout={quote(str(self.opts.plugin_timeout))}') # sos-3.8 added options if self.check_sos_version('3.8'): if self.opts.allow_system_changes: sos_opts.append('--allow-system-changes') if self.opts.no_env_vars: sos_opts.append('--no-env-vars') if self.opts.since: sos_opts.append(f'--since={quote(self.opts.since)}') if self.check_sos_version('4.1'): if self.opts.skip_commands: sos_opts.append('--skip-commands=' f'{quote(",".join(self.opts.skip_commands))}') if self.opts.skip_files: sos_opts.append( f'--skip-files={quote(",".join(self.opts.skip_files))}' ) if self.check_sos_version('4.2'): if self.opts.cmd_timeout: sos_opts.append( f'--cmd-timeout={quote(str(self.opts.cmd_timeout))}' ) # handle downstream versions that backported this option if self.check_sos_version('4.3') or self.check_sos_version('4.2-13'): if self.opts.container_runtime != 'auto': sos_opts.append( f"--container-runtime={self.opts.container_runtime}" ) if self.opts.namespaces: sos_opts.append( f"--namespaces={self.opts.namespaces}" ) if self.check_sos_version('4.5.2'): if self.opts.journal_size: sos_opts.append(f"--journal-size={self.opts.journal_size}") if self.opts.low_priority: sos_opts.append('--low-priority') self.update_cmd_from_cluster() sos_cmd = sos_cmd.replace( 'sosreport', os.path.join(self.host.sos_bin_path, self.sos_bin) ) for opt in self.sos_options: _val = self.sos_options[opt] sos_opts.append(f"--{opt} {_val if _val else ''}") if self.plugopts: opts = [o for o in self.plugopts if self._plugin_exists(o.split('.')[0]) and self._plugin_option_exists(o.split('=')[0])] if opts: sos_opts.append(f'-k {quote(",".join(o for o in opts))}') if self.preset: if self._preset_exists(self.preset): sos_opts.append(f'--preset={quote(self.preset)}') else: self.log_debug(f'Requested to enable preset {self.preset} but ' 'preset does not exist on node') if self.only_plugins: plugs = [o for o in self.only_plugins if self._plugin_exists(o)] if len(plugs) != len(self.only_plugins): not_only = list(set(self.only_plugins) - set(plugs)) self.log_debug(f'Requested plugins {not_only} were requested ' 'to be enabled but do not exist') only = self._fmt_sos_opt_list(self.only_plugins) if only: sos_opts.append(f'--only-plugins={quote(only)}') self.sos_cmd = f"{sos_cmd} {' '.join(sos_opts)}" self.log_info(f'Final sos command set to {self.sos_cmd}') self.manifest.add_field('final_sos_command', self.sos_cmd) return if self.skip_plugins: # only run skip-plugins for plugins that are enabled skip = [o for o in self.skip_plugins if self._check_enabled(o)] if len(skip) != len(self.skip_plugins): not_skip = list(set(self.skip_plugins) - set(skip)) self.log_debug(f'Requested to skip plugins {not_skip}, but ' 'plugins are already not enabled') skipln = self._fmt_sos_opt_list(skip) if skipln: sos_opts.append(f'--skip-plugins={quote(skipln)}') if self.enable_plugins: # only run enable for plugins that are disabled opts = [o for o in self.enable_plugins if o not in self.skip_plugins and self._check_disabled(o) and self._plugin_exists(o)] if len(opts) != len(self.enable_plugins): not_on = list(set(self.enable_plugins) - set(opts)) self.log_debug(f'Requested to enable plugins {not_on}, but ' 'plugins are already enabled or do not exist') enable = self._fmt_sos_opt_list(opts) if enable: sos_opts.append(f'--enable-plugins={quote(enable)}') self.sos_cmd = f"{sos_cmd} {' '.join(sos_opts)}" self.log_info(f'Final sos command set to {self.sos_cmd}') self.manifest.add_field('final_sos_command', self.sos_cmd) def determine_sos_label(self): """Determine what, if any, label should be added to the sos report""" label = '' label += self.cluster.get_node_label(self) if self.opts.label: label += (f'{self.opts.label}' if not label else f'-{self.opts.label}') if not label: return None self.log_debug(f'Label for sos report set to {label}') if self.check_sos_version('3.6'): lcmd = '--label' else: lcmd = '--name' label = f'{self.address.split(".")[0]}-{label}' return f'{lcmd}={label}' def finalize_sos_path(self, path): """Use host facts to determine if we need to change the sos path we are retrieving from""" pstrip = self.host.sos_path_strip if pstrip: path = path.replace(pstrip, '') path = path.split()[0] self.log_info(f'Final sos path: {path}') self.sos_path = path self.archive = path.split('/')[-1] self.manifest.add_field('collected_archive', self.archive) def determine_sos_error(self, rc, stdout): if rc == -1: return 'sos report process received SIGKILL on node' if rc == 1: if 'sudo' in stdout: return 'sudo attempt failed' if rc == 127: return 'sos report terminated unexpectedly. Check disk space' if len(stdout) > 0: return stdout.split('\n')[0:1] return f'sos exited with code {rc}' def execute_sos_command(self): """Run sos report and capture the resulting file path""" self.ui_msg('Generating sos report...') try: path = False checksum = False res = self.run_command(self.sos_cmd, timeout=self.opts.timeout, use_shell=True, need_root=True, use_container=True, env=self.sos_env_vars) if res['status'] == 0: for line in res['output'].splitlines(): if fnmatch.fnmatch(line, '*sosreport-*tar*'): path = line.strip() if line.startswith((" sha256\t", " md5\t")): checksum = line.split("\t")[1] elif line.startswith("The checksum is: "): checksum = line.split()[3] if checksum: self.manifest.add_field('checksum', checksum) if len(checksum) == 32: self.manifest.add_field('checksum_type', 'md5') elif len(checksum) == 64: self.manifest.add_field('checksum_type', 'sha256') else: self.manifest.add_field('checksum_type', 'unknown') else: self.manifest.add_field('checksum_type', 'unknown') else: err = self.determine_sos_error(res['status'], res['output']) self.log_debug("Error running sos report. rc = " f"{res['status']} msg = {res['output']}") raise Exception(err) return path except CommandTimeoutException: self.log_error('Timeout exceeded') raise except Exception as err: self.log_info(f"Exception during sos report execution: {err}") self.ui_msg(f"Error running sos report: {err}") raise def retrieve_file(self, path): """Copies the specified file from the host to our temp dir""" destdir = self.tmpdir + '/' dest = os.path.join(destdir, path.split('/')[-1]) try: if self.file_exists(path): self.log_info(f"Copying remote {path} to local {destdir}") return self._transport.retrieve_file(path, dest) self.log_debug(f"Attempting to copy remote file {path}, but it" " does not exist on filesystem") return False except Exception as err: self.log_debug(f"Failed to retrieve {path}: {err}") return False def remove_file(self, path): """Removes the spciefied file from the host. This should only be used after we have retrieved the file already """ path = ''.join(path.split()) try: if len(path.split('/')) <= 2: # ensure we have a non '/' path self.log_debug(f"Refusing to remove path {path}: appears to " "be incorrect and possibly dangerous") return False if self.file_exists(path): self.log_info(f"Removing file {path}") cmd = f"rm -f {path}" res = self.run_command(cmd, need_root=True) return res['status'] == 0 self.log_debug(f"Attempting to remove remote file {path}, but " "it does not exist on filesystem") return False except Exception as e: self.log_debug(f'Failed to remove {path}: {e}') return False def retrieve_sosreport(self): """Collect the sos report archive from the node""" if self.need_sudo or self.opts.become_root: try: self.make_archive_readable(self.sos_path) except Exception: self.log_error('Failed to make archive readable') return False self.log_info(f'Retrieving sos report from {self.address}') self.ui_msg('Retrieving sos report...') try: ret = self.retrieve_file(self.sos_path) except Exception as err: self.log_error(err) return False if ret: self.ui_msg('Successfully collected sos report') self.file_list.append(self.sos_path.split('/')[-1]) return True self.ui_msg('Failed to retrieve sos report') return False def remove_sos_archive(self): """Remove the sos report archive from the node, since we have collected it and it would be wasted space otherwise""" if self.sos_path is None or self.local: # local transport moves the archive rather than copies it, so there # is no archive at the original location to remove return if 'sosreport' not in self.sos_path: self.log_debug(f"Node sos report path {self.sos_path} looks " "incorrect. Not attempting to remove path") return removed = self.remove_file(self.sos_path) if not removed: self.log_error('Failed to remove sos report') def cleanup(self): """Remove the sos archive from the node once we have it locally""" self.remove_sos_archive() if self.sos_path: for ext in ['.sha256', '.md5']: if self.remove_file(self.sos_path + ext): break cleanup = self.host.set_cleanup_cmd() if cleanup: self.run_command(cleanup, need_root=True) def collect_extra_cmd(self, filenames): """Collect the file created by a cluster outside of sos""" for filename in filenames: try: if self.need_sudo or self.opts.become_root: try: self.make_archive_readable(filename) except Exception as err: self.log_error(f"Unable to retrieve file {filename}") self.log_debug(f"Failed to make file {filename} " f"readable: {err}") continue ret = self.retrieve_file(filename) if ret: self.file_list.append(filename.split('/')[-1]) self.remove_file(filename) else: self.log_error(f"Unable to retrieve file {filename}") except Exception as e: msg = f'Error collecting additional data from primary: {e}' self.log_error(msg) def make_archive_readable(self, filepath): """Used to make the given archive world-readable, which is slightly better than changing the ownership outright. This is only used when we're not connecting as root. """ cmd = f'chmod o+r {filepath}' res = self.run_command(cmd, timeout=10, need_root=True) if res['status'] == 0: return True msg = "Exception while making %s readable. Return code was %s" self.log_error(msg % (filepath, res['status'])) raise Exception # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/transports/0000775000175000017500000000000014660147624016000 5ustar arifarifsos-4.8.0/sos/collector/transports/oc.py0000664000175000017500000002213714660147624016760 0ustar arifarif# Copyright Red Hat 2021, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import tempfile import os from sos.collector.transports import RemoteTransport from sos.utilities import (is_executable, sos_get_command_output, SoSTimeoutError) class OCTransport(RemoteTransport): """ This transport leverages the execution of commands via a locally available and configured ``oc`` binary for OCPv4 environments. The location of the oc binary MUST be in the $PATH used by the locally loaded SoS policy. Specifically this means that the binary cannot be in the running user's home directory, such as ~/.local/bin. OCPv4 clusters generally discourage the use of SSH, so this transport may be used to remove our use of SSH in favor of the environment provided method of connecting to nodes and executing commands via debug pods. The debug pod created will be a privileged pod that mounts the host's filesystem internally so that sos report collections reflect the host, and not the container in which it runs. This transport will execute within a temporary 'sos-collect-tmp' project created by the OCP cluster profile. The project will be removed at the end of execution. In the event of failures due to a misbehaving OCP API or oc binary, it is recommended to fallback to the control_persist transport by manually setting the --transport option. """ name = 'oc' project = 'sos-collect-tmp' def run_oc(self, cmd, **kwargs): """Format and run a command with `oc` in the project defined for our execution """ return sos_get_command_output( f"oc -n {self.project} {cmd}", **kwargs ) @property def connected(self): up = self.run_oc( f"wait --timeout=0s --for=condition=ready pod/{self.pod_name}" ) return up['status'] == 0 def get_node_pod_config(self): """Based on our template for the debug container, add the node-specific items so that we can deploy one of these on each node we're collecting from """ return { "kind": "Pod", "apiVersion": "v1", "metadata": { "name": f"{self.address.split('.')[0]}-sos-collector", "namespace": self.project }, "priorityClassName": "system-cluster-critical", "spec": { "volumes": [ { "name": "host", "hostPath": { "path": "/", "type": "Directory" } }, { "name": "run", "hostPath": { "path": "/run", "type": "Directory" } }, { "name": "varlog", "hostPath": { "path": "/var/log", "type": "Directory" } }, { "name": "machine-id", "hostPath": { "path": "/etc/machine-id", "type": "File" } } ], "containers": [ { "name": "sos-collector-tmp", "image": "registry.redhat.io/rhel8/support-tools" if not self.opts.image else self.opts.image, "command": [ "/bin/bash" ], "env": [ { "name": "HOST", "value": "/host" } ], "resources": {}, "volumeMounts": [ { "name": "host", "mountPath": "/host" }, { "name": "run", "mountPath": "/run" }, { "name": "varlog", "mountPath": "/var/log" }, { "name": "machine-id", "mountPath": "/etc/machine-id" } ], "securityContext": { "privileged": True, "runAsUser": 0 }, "stdin": True, "stdinOnce": True, "tty": True } ], "imagePullPolicy": "Always" if self.opts.force_pull_image else "IfNotPresent", "restartPolicy": "Never", "nodeName": self.address, "hostNetwork": True, "hostPID": True, "hostIPC": True } } def _connect(self, password): # the oc binary must be _locally_ available for this to work if not is_executable('oc'): return False # deploy the debug container we'll exec into podconf = self.get_node_pod_config() self.pod_name = podconf['metadata']['name'] fd, self.pod_tmp_conf = tempfile.mkstemp(dir=self.tmpdir) with open(fd, 'w', encoding='utf-8') as cfile: json.dump(podconf, cfile) self.log_debug(f"Starting sos collector container '{self.pod_name}'") # this specifically does not need to run with a project definition out = sos_get_command_output( f"oc create -f {self.pod_tmp_conf}" ) if (out['status'] != 0 or f"pod/{self.pod_name} created" not in out['output']): self.log_error("Unable to deploy sos collect pod") self.log_debug(f"Debug pod deployment failed: {out['output']}") return False self.log_debug(f"Pod '{self.pod_name}' successfully deployed, waiting " "for pod to enter ready state") # wait for the pod to report as running try: up = self.run_oc(f"wait --for=condition=Ready pod/{self.pod_name} " "--timeout=30s", # timeout is for local safety, not oc timeout=40) if not up['status'] == 0: self.log_error("Pod not available after 30 seconds") return False except SoSTimeoutError: self.log_error("Timeout while polling for pod readiness") return False except Exception as err: self.log_error(f"Error while waiting for pod to be ready: {err}") return False return True def _format_cmd_for_exec(self, cmd): if cmd.startswith('oc'): return (f"oc -n {self.project} exec --request-timeout=0 " f"{self.pod_name} -- chroot /host {cmd}") return super()._format_cmd_for_exec(cmd) def run_command(self, cmd, timeout=180, need_root=False, env=None, use_shell=False): # debug pod setup is slow, extend all timeouts to account for this if timeout: timeout += 10 # since we always execute within a bash shell, force disable use_shell # to avoid double-quoting return super().run_command(cmd, timeout, need_root, env, use_shell=False) def _disconnect(self): if os.path.exists(self.pod_tmp_conf): os.unlink(self.pod_tmp_conf) removed = self.run_oc(f"delete pod {self.pod_name}") if "deleted" not in removed['output']: self.log_debug(f"Calling delete on pod '{self.pod_name}' failed: " f"{removed}") return False return True @property def remote_exec(self): return (f"oc -n {self.project} exec --request-timeout=0 " f"{self.pod_name} -- /bin/bash -c") def _retrieve_file(self, fname, dest): # check if --retries flag is available for given version of oc result = self.run_oc("cp --retries", stderr=True) flags = '' if "unknown flag" in result["output"] else '--retries=5' cmd = self.run_oc(f"cp {flags} {self.pod_name}:{fname} {dest}") return cmd['status'] == 0 sos-4.8.0/sos/collector/transports/__init__.py0000664000175000017500000003622714660147624020123 0ustar arifarif# Copyright Red Hat 2021, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import inspect import logging import re from shlex import quote import pexpect from sos.collector.exceptions import (ConnectionException, CommandTimeoutException) from sos.utilities import bold class RemoteTransport(): """The base class used for defining supported remote transports to connect to remote nodes in conjunction with `sos collect`. This abstraction is used to manage the backend connections to nodes so that SoSNode() objects can be leveraged generically to connect to nodes, inspect those nodes, and run commands on them. """ name = 'undefined' default_user = None def __init__(self, address, commons): self.address = address self.opts = commons['cmdlineopts'] self.tmpdir = commons['tmpdir'] self.need_sudo = commons['need_sudo'] self._hostname = None self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') def _sanitize_log_msg(self, msg): """Attempts to obfuscate sensitive information in log messages such as passwords""" reg = r'(?P(pass|key|secret|PASS|KEY|SECRET).*?=)(?P.*?\s)' return re.sub(reg, r'\g****** ', msg) def log_info(self, msg): """Used to print and log info messages""" caller = inspect.stack()[1][3] lmsg = f'[{self.hostname}:{caller}] {msg}' self.soslog.info(lmsg) def log_error(self, msg): """Used to print and log error messages""" caller = inspect.stack()[1][3] lmsg = f'[{self.hostname}:{caller}] {msg}' self.soslog.error(lmsg) def log_debug(self, msg): """Used to print and log debug messages""" msg = self._sanitize_log_msg(msg) caller = inspect.stack()[1][3] msg = f'[{self.hostname}:{caller}] {msg}' self.soslog.debug(msg) @property def hostname(self): if self._hostname and 'localhost' not in self._hostname: return self._hostname return self.address @property def connected(self): """Is the transport __currently__ connected to the node, or otherwise capable of seamlessly running a command or similar on the node? """ return False @property def remote_exec(self): """This is the command string needed to leverage the remote transport when executing commands. For example, for an SSH transport this would be the `ssh ` string prepended to any command so that the command is executed by the ssh binary. This is also referenced by the `remote_exec` parameter for policies when loading a policy for a remote node """ return None @classmethod def display_help(cls, section): if cls is RemoteTransport: return cls.display_self_help(section) section.set_title(f"{cls.name.title().replace('_', ' ')} " "Transport Detailed Help") if cls.__doc__ and cls.__doc__ is not RemoteTransport.__doc__: section.add_text(cls.__doc__) else: section.add_text( 'Detailed information not available for this transport' ) return None @classmethod def display_self_help(cls, section): section.set_title('SoS Remote Transport Help') section.add_text( "\nTransports define how SoS connects to nodes and executes " f"commands on them for the purposes of an {bold('sos collect')} " "run. Generally, this means transports define how commands are " "wrapped locally so that they are executed on the remote node(s) " "instead." ) section.add_text( "Transports are generally selected by the cluster profile loaded " "for a given execution, however users may explicitly set one " f"using '{bold('--transport=$transport_name')}'. Note that not all" " transports will function for all cluster/node types." ) section.add_text( 'By default, OpenSSH Control Persist is attempted. Additional ' 'information for each supported transport is available in the ' 'following help sections:\n' ) from sos.collector.sosnode import TRANSPORTS for transport in TRANSPORTS: _sec = bold(f"collect.transports.{transport}") _desc = f"The '{transport.lower()}' transport" section.add_text( f"{' ':>8}{_sec:<45}{_desc:<30}", newline=False ) def connect(self, password): """Perform the connection steps in order to ensure that we are able to connect to the node for all future operations. Note that this should not provide an interactive shell at this time. """ if self._connect(password): if not self._hostname: self._get_hostname() return True return False def _connect(self, password): """Actually perform the connection requirements. Should be overridden by specific transports that subclass RemoteTransport """ raise NotImplementedError( f"Transport {self.name} does not define connect") def reconnect(self, password): """Attempts to reconnect to the node using the standard connect() but does not do so indefinitely. This imposes a strict number of retry attempts before failing out """ attempts = 1 last_err = 'unknown' while attempts < 5: self.log_debug(f"Attempting reconnect (#{attempts}) to node") try: if self.connect(password): return True except Exception as err: self.log_debug(f"Attempt #{attempts} exception: {err}") last_err = err attempts += 1 self.log_error("Unable to reconnect to node after 5 attempts, " "aborting.") raise ConnectionException(f"last exception from transport: {last_err}") def disconnect(self): """Perform whatever steps are necessary, if any, to terminate any connection to the node """ try: if self._disconnect(): self.log_debug("Successfully disconnected from node") else: self.log_error("Unable to successfully disconnect, see log for" " more details") except Exception as err: self.log_error(f"Failed to disconnect: {err}") def _disconnect(self): raise NotImplementedError( f"Transport {self.name} does not define disconnect") @property def _need_shell(self): """ Transports may override this to control when/if commands executed over the transport needs to utilize a shell on the remote host. """ return False def run_command(self, cmd, timeout=180, need_root=False, env=None, use_shell='auto'): """Run a command on the node, returning its output and exit code. This should return the exit code of the command being executed, not the exit code of whatever mechanism the transport uses to execute that command :param cmd: The command to run :type cmd: ``str`` :param timeout: The maximum time in seconds to allow the cmd to run :type timeout: ``int``` :param need_root: Does ``cmd`` require root privileges? :type need_root: ``bool`` :param env: Specify env vars to be passed to the ``cmd`` :type env: ``dict`` :param use_shell: Does ``cmd`` require execution within a shell? :type use_shell: ``bool`` or ``auto`` for transport-determined :returns: Output of ``cmd`` and the exit code :rtype: ``dict`` with keys ``output`` and ``status`` """ self.log_debug(f'Running command {cmd}') if (use_shell is True or (self._need_shell if use_shell == 'auto' else False)): cmd = f"/bin/bash -c {quote(cmd)}" self.log_debug(f"Shell requested, command is now {cmd}") # currently we only use/support the use of pexpect for handling the # execution of these commands, as opposed to directly invoking # subprocess.Popen() in conjunction with tools like sshpass. # If that changes in the future, we'll add decision making logic here # to route to the appropriate handler, but for now we just go straight # to using pexpect return self._run_command_with_pexpect(cmd, timeout, need_root, env) def _format_cmd_for_exec(self, cmd): """Format the command in the way needed for the remote transport to successfully execute it as one would when manually executing it :param cmd: The command being executed, as formatted by SoSNode :type cmd: ``str`` :returns: The command further formatted as needed by this transport :rtype: ``str`` """ cmd = f"{self.remote_exec} {quote(cmd)}" cmd = cmd.lstrip() return cmd def _run_command_with_pexpect(self, cmd, timeout, need_root, env): """Execute the command using pexpect, which allows us to more easily handle prompts and timeouts compared to directly leveraging the subprocess.Popen() method. :param cmd: The command to execute. This will be automatically formatted to use the transport. :type cmd: ``str`` :param timeout: The maximum time in seconds to run ``cmd`` :type timeout: ``int`` :param need_root: Does ``cmd`` need to run as root or with sudo? :type need_root: ``bool`` :param env: Any env vars that ``cmd`` should be run with :type env: ``dict`` """ cmd = self._format_cmd_for_exec(cmd) # if for any reason env is empty, set it to None as otherwise # pexpect interprets this to mean "run this command with no env vars of # any kind" if not env: env = None try: result = pexpect.spawn(cmd, encoding='utf-8', env=env) except pexpect.exceptions.ExceptionPexpect as err: self.log_debug(err.value) return {'status': 127, 'output': ''} _expects = [pexpect.EOF, pexpect.TIMEOUT] if need_root and self.opts.ssh_user != 'root': _expects.extend([ '\\[sudo\\] password for .*:', 'Password:' ]) index = result.expect(_expects, timeout=timeout) if index in [2, 3]: self._send_pexpect_password(index, result) index = result.expect(_expects, timeout=timeout) if index == 0: out = result.before result.close() return {'status': result.exitstatus, 'output': out} if index == 1: raise CommandTimeoutException(cmd) # if we somehow manage to flow to this point, use this bogus exit code # as a signal to debugging efforts that whatever went sideways did so # as part of the above block self.log_debug(f"Unexpected index {index} from pexpect: {result}") return {'status': 999, 'output': ''} def _send_pexpect_password(self, index, result): """Handle password prompts for sudo and su usage for non-root SSH users :param index: The index pexpect.spawn returned to match against either a sudo or su prompt :type index: ``int`` :param result: The spawn running the command :type result: ``pexpect.spawn`` """ if index == 2: if not self.opts.sudo_pw and not self.opts.nopasswd_sudo: msg = ("Unable to run command: sudo password " "required but not provided") self.log_error(msg) raise Exception(msg) result.sendline(self.opts.sudo_pw) elif index == 3: if not self.opts.root_password: msg = "Unable to run command as root: no root password given" self.log_error(msg) raise Exception(msg) result.sendline(self.opts.root_password) def _get_hostname(self): """Determine the hostname of the node and set that for future reference and logging :returns: The hostname of the system, per the `hostname` command :rtype: ``str`` """ _out = self.run_command('hostname') if _out['status'] == 0: self._hostname = _out['output'].strip() if not self._hostname: self._hostname = self.address self.log_info(f"Hostname set to {self._hostname}") return self._hostname def retrieve_file(self, fname, dest): """Copy a remote file, fname, to dest on the local node :param fname: The name of the file to retrieve :type fname: ``str`` :param dest: Where to save the file to locally :type dest: ``str`` :returns: True if file was successfully copied from remote, or False :rtype: ``bool`` """ attempts = 0 try: while attempts < 5: attempts += 1 ret = self._retrieve_file(fname, dest) if ret: return True self.log_info(f"File retrieval attempt {attempts} failed") self.log_info("File retrieval failed after 5 attempts") return False except Exception as err: self.log_error("Exception encountered during retrieval attempt " f"{attempts} for {fname}: {err}") raise err def _retrieve_file(self, fname, dest): raise NotImplementedError( f"Transport {self.name} does not support file copying") def read_file(self, fname): """Read the given file fname and return its contents :param fname: The name of the file to read :type fname: ``str`` :returns: The content of the file :rtype: ``str`` """ self.log_debug(f"Reading file {fname}") return self._read_file(fname) def _read_file(self, fname): res = self.run_command(f"cat {fname}", timeout=10) if res['status'] == 0: return res['output'] if 'No such file' in res['output']: self.log_debug(f"File {fname} does not exist on node") else: self.log_error(f"Error reading {fname}: " f"{res['output'].split(':')[1:]}") return '' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/transports/juju.py0000664000175000017500000000531614660147624017334 0ustar arifarif# Copyright (c) 2023 Canonical Ltd., Chi Wai Chan # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import subprocess from sos.collector.exceptions import JujuNotInstalledException from sos.collector.transports import RemoteTransport from sos.utilities import sos_get_command_output class JujuSSH(RemoteTransport): """ A "transport" that leverages `juju ssh` to perform commands on the remote hosts. This transport is expected to be used in juju managed environment, and the user should have the necessary credential for accessing the controller. When using this transport, the --nodes option will be expected to be a comma separated machine IDs, **not** IP addr, since `juju ssh` identifies the ssh target by machine ID. Examples: sos collect --nodes 0,1,2 --no-local --transport juju --batch """ name = "juju_ssh" default_user = "ubuntu" def _check_juju_installed(self): cmd = "juju version" try: subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError: self.log_error("Failed to check `juju` version") raise JujuNotInstalledException return True def _chmod(self, fname): cmd = f"{self.remote_exec} sudo chmod o+r {fname}" try: subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError: self.log_error(f"Failed to make {fname} world-readable") raise return True def _connect(self, password=""): self._connected = self._check_juju_installed() return self._connected def _disconnect(self): return True @property def connected(self): return self._connected @property def remote_exec(self): model, target_option = self.address.split(":") model_option = f"-m {model}" if model else "" option = f"{model_option} {target_option}" return f"juju ssh {option}" def _retrieve_file(self, fname, dest): self._chmod(fname) # juju scp needs the archive to be world-readable model, unit = self.address.split(":") model_option = f"-m {model}" if model else "" cmd = f"juju scp {model_option} -- -r {unit}:{fname} {dest}" res = sos_get_command_output(cmd) return res["status"] == 0 # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/transports/saltstack.py0000664000175000017500000001147114660147624020347 0ustar arifarif# Copyright Red Hat 2022, Trevor Benson # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import contextlib import json import os import shutil from sos.collector.transports import RemoteTransport from sos.collector.exceptions import (ConnectionException, SaltStackMasterUnsupportedException) from sos.utilities import (is_executable, sos_get_command_output) class SaltStackMaster(RemoteTransport): """ A transport for collect that leverages SaltStack's Master Pub/Sub functionality to send commands to minions. This transport will by default assume the use cmd.shell module to execute commands on the minions. """ name = 'saltstack' def _convert_output_json(self, json_output): return list(json.loads(json_output).values())[0] def run_command(self, cmd, timeout=180, need_root=False, env=None, use_shell=False): """ Run a command on the remote host using SaltStack Master. If the output is json, convert it to a string. """ ret = super().run_command( cmd, timeout, need_root, env, use_shell) with contextlib.suppress(Exception): ret['output'] = self._convert_output_json(ret['output']) return ret def _salt_retrieve_file(self, node, fname, dest): """ Execute cp.push on the remote host using SaltStack Master """ cmd = f"salt {node} cp.push {fname}" res = sos_get_command_output(cmd) if res['status'] == 0: cachedir = f"/var/cache/salt/master/minions/{self.address}/files" cachedir_file = os.path.join(cachedir, fname.lstrip('/')) shutil.move(cachedir_file, dest) return True return False @property def connected(self): """Check if the remote host is responding using SaltStack Master.""" up = self.run_command("echo Connected", timeout=10) return up['status'] == 0 # pylint: disable=unused-argument def _check_for_saltstack(self, password=None): """Checks to see if the local system supported SaltStack Master. This check relies on feedback from the salt binary. The command being run should always generate stderr output, but depending on what that output reads we can determine if SaltStack Master is supported or not. For our purposes, a host that does not support SaltStack Master is not able to run sos collect. Returns True if SaltStack Master is supported, else raise Exception """ cmd = 'salt-run manage.status' res = sos_get_command_output(cmd) if res['status'] == 0: return res['status'] == 0 raise SaltStackMasterUnsupportedException def _connect(self, password=None): """Connect to the remote host using SaltStack Master. This method will attempt to connect to the remote host using SaltStack Master. If the connection fails, an exception will be raised. If the connection is successful, the connection will be stored in the self._connection attribute. """ if not is_executable('salt'): self.log_error("salt command is not executable. ") return False try: self._check_for_saltstack() except ConnectionException: self.log_error("Transport is not locally supported. ") raise self.log_info("Transport is locally supported and service running. ") cmd = "echo Connected" result = self.run_command(cmd, timeout=180) if result['status'] == 1: raise ConnectionException(self.address) return result['status'] == 0 def _disconnect(self): return True @property def remote_exec(self): """The remote execution command to use for this transport.""" salt_args = "--out json --static --no-color" return f"salt {salt_args} {self.address} cmd.shell " def _retrieve_file(self, fname, dest): """Retrieve a file from the remote host using saltstack Parameters fname The path to the file on the remote host dest The path to the destination directory on the master Returns True if the file was retrieved, else False """ return ( self._salt_retrieve_file(self.address, fname, dest) if self.connected else False ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/transports/local.py0000664000175000017500000000255214660147624017450 0ustar arifarif# Copyright Red Hat 2021, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import shutil from sos.collector.transports import RemoteTransport class LocalTransport(RemoteTransport): """ A 'transport' to represent a local node. No remote connection is actually made, and all commands set to be run by this transport are executed locally without any wrappers. """ name = 'local_node' def _connect(self, password): return True def _disconnect(self): return True @property def connected(self): return True def _retrieve_file(self, fname, dest): self.log_debug(f"Moving {fname} to {dest}") shutil.copy(fname, dest) return True def _format_cmd_for_exec(self, cmd): return cmd def _read_file(self, fname): if os.path.exists(fname): with open(fname, 'r', encoding='utf-8') as rfile: return rfile.read() self.log_debug(f"No such file: {fname}") return '' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/collector/transports/control_persist.py0000664000175000017500000002030314660147624021601 0ustar arifarif# Copyright Red Hat 2021, Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import subprocess import pexpect from sos.collector.transports import RemoteTransport from sos.collector.exceptions import (InvalidPasswordException, TimeoutPasswordAuthException, PasswordRequestException, AuthPermissionDeniedException, ConnectionException, ConnectionTimeoutException, ControlSocketMissingException, ControlPersistUnsupportedException) from sos.utilities import sos_get_command_output class SSHControlPersist(RemoteTransport): """ A transport for collect that leverages OpenSSH's ControlPersist functionality which uses control sockets to transparently keep a connection open to the remote host without needing to rebuild the SSH connection for each and every command executed on the node. This transport will by default assume the use of SSH keys, meaning keys have already been distributed to target nodes. If this is not the case, users will need to provide a password using the --password or --password-per-node option, depending on if the password to connect to all nodes is the same or not. Note that these options prevent the use of the --batch option, as they require user input. """ name = 'control_persist' def _check_for_control_persist(self): """Checks to see if the local system supported SSH ControlPersist. ControlPersist allows OpenSSH to keep a single open connection to a remote host rather than building a new session each time. This is the same feature that Ansible uses in place of paramiko, which we have a need to drop in sos collect. This check relies on feedback from the ssh binary. The command being run should always generate stderr output, but depending on what that output reads we can determine if ControlPersist is supported or not. For our purposes, a host that does not support ControlPersist is not able to run sos collect. Returns True if ControlPersist is supported, else raise Exception. """ ssh_cmd = ['ssh', '-o', 'ControlPersist'] with subprocess.Popen(ssh_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as cmd: _, err = cmd.communicate() err = err.decode('utf-8') if 'Bad configuration option' in err or 'Usage:' in err: raise ControlPersistUnsupportedException return True def _connect(self, password=''): # pylint: disable=too-many-branches """ Using ControlPersist, create the initial connection to the node. This will generate an OpenSSH ControlPersist socket within the tmp directory created or specified for sos collect to use. At most, we will wait 30 seconds for a connection. This involves a 15 second wait for the initial connection attempt, and a subsequent 15 second wait for a response when we supply a password. Since we connect to nodes in parallel (using the --threads value), this means that the time between 'Connecting to nodes...' and 'Beginning collection of sosreports' that users see can be up to an amount of time equal to 30*(num_nodes/threads) seconds. Returns True if session is successfully opened, else raise Exception """ try: self._check_for_control_persist() except ControlPersistUnsupportedException: self.log_error("OpenSSH ControlPersist is not locally supported. " "Please update your OpenSSH installation.") raise self.log_info('Opening SSH session to create control socket') self.control_path = f"{self.tmpdir}/.sos-collector-{self.address}" self.ssh_cmd = '' connected = False ssh_key = '' ssh_port = '' if self.opts.ssh_port != 22: ssh_port = f"-p{self.opts.ssh_port} " if self.opts.ssh_key: ssh_key = f"-i{self.opts.ssh_key}" cmd = (f"ssh {ssh_key} {ssh_port} -oControlPersist=600 " "-oControlMaster=auto -oStrictHostKeyChecking=no " f"-oControlPath={self.control_path} {self.opts.ssh_user}@" f"{self.address} \"echo Connected\"") res = pexpect.spawn(cmd, encoding='utf-8') connect_expects = [ 'Connected', 'password:', '.*Permission denied.*', '.* port .*: No route to host', '.*Could not resolve hostname.*', pexpect.TIMEOUT ] index = res.expect(connect_expects, timeout=15) if index == 0: connected = True elif index == 1: if password: pass_expects = [ 'Connected', 'Permission denied, please try again.', pexpect.TIMEOUT ] res.sendline(password) pass_index = res.expect(pass_expects, timeout=15) if pass_index == 0: connected = True elif pass_index == 1: # Note that we do not get an exitstatus here, so matching # this line means an invalid password will be reported for # both invalid passwords and invalid user names raise InvalidPasswordException elif pass_index == 2: raise TimeoutPasswordAuthException else: raise PasswordRequestException elif index == 2: raise AuthPermissionDeniedException elif index == 3: raise ConnectionException(self.address, self.opts.ssh_port) elif index == 4: raise ConnectionException(self.address) elif index == 5: raise ConnectionTimeoutException else: raise Exception(f"Unknown error, client returned {res.before}") if connected: if not os.path.exists(self.control_path): raise ControlSocketMissingException self.log_debug("Successfully created control socket at " f"{self.control_path}") return True return False def _disconnect(self): if os.path.exists(self.control_path): try: os.remove(self.control_path) return True except Exception as err: self.log_debug(f"Could not disconnect properly: {err}") return False self.log_debug("Control socket not present when attempting to " "terminate session") return False @property def connected(self): """Check if the SSH control socket exists The control socket is automatically removed by the SSH daemon in the event that the last connection to the node was greater than the timeout set by the ControlPersist option. This can happen for us if we are collecting from a large number of nodes, and the timeout expires before we start collection. """ return os.path.exists(self.control_path) @property def remote_exec(self): if not self.ssh_cmd: self.ssh_cmd = (f"ssh -oControlPath={self.control_path} " f"{self.opts.ssh_user}@{self.address}") return self.ssh_cmd def _retrieve_file(self, fname, dest): cmd = (f"/usr/bin/scp -oControlPath={self.control_path} " f"{self.opts.ssh_user}@{self.address}:{fname} {dest}") res = sos_get_command_output(cmd) return res['status'] == 0 # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/0000775000175000017500000000000014660147624013106 5ustar arifarifsos-4.8.0/sos/report/__init__.py0000664000175000017500000023473414660147624015234 0ustar arifarif# Copyright (C) 2006 Steve Conklin # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # pylint: disable=too-many-branches,too-many-locals import sys import traceback import os import errno import logging import hashlib import pdb from datetime import datetime import glob from concurrent.futures import ThreadPoolExecutor from concurrent.futures import TimeoutError as FuturesTimeoutError from shutil import rmtree import sos.report.plugins from sos.utilities import (ImporterHelper, SoSTimeoutError, bold, sos_get_command_output, TIMEOUT_DEFAULT, listdir, is_executable) from sos import _sos as _ from sos import __version__ from sos.component import SoSComponent import sos.policies from sos.report.reporting import (Report, Section, Command, CopiedFile, CreatedFile, Alert, Note, PlainTextReport, JSONReport, HTMLReport) from sos.cleaner import SoSCleaner # file system errors that should terminate a run fatal_fs_errors = (errno.ENOSPC, errno.EROFS) def _format_list(first_line, items, indent=False, sep=", "): lines = [] line = first_line if indent: newline = len(first_line) * ' ' else: newline = "" for item in items: if len(line) + len(item) + len(sep) > 72: lines.append(line) line = newline line = line + item + sep if line[-len(sep):] == sep: line = line[:-len(sep)] lines.append(line) return lines def _format_since(date): """ This function will format --since arg to append 0s if enduser didn't. It's used in the _get_parser. This will also be a good place to add human readable and relative date parsing (like '2 days ago') in the future """ return datetime.strptime(f"{date:<014s}", '%Y%m%d%H%M%S') # valid modes for --chroot chroot_modes = ["auto", "always", "never"] class SoSReport(SoSComponent): """Run a set of commands and file collections and save them to a report for future analysis """ desc = "Collect files and command output in an archive" root_required = True arg_defaults = { 'alloptions': False, 'all_logs': False, 'build': False, 'case_id': '', 'chroot': 'auto', 'clean': False, 'container_runtime': 'auto', 'keep_binary_files': False, 'desc': '', 'domains': [], 'disable_parsers': [], 'skip_cleaning_files': [], 'dry_run': False, 'estimate_only': False, 'experimental': False, 'enable_plugins': [], 'journal_size': 100, 'keywords': [], 'keyword_file': None, 'plugopts': [], 'label': '', 'list_plugins': False, 'list_presets': False, 'list_profiles': False, 'log_size': 25, 'low_priority': False, 'map_file': '/etc/sos/cleaner/default_mapping', 'skip_commands': [], 'skip_files': [], 'skip_plugins': [], 'namespaces': None, 'no_report': False, 'no_env_vars': False, 'no_postproc': False, 'no_update': False, 'note': '', 'only_plugins': [], 'preset': 'auto', 'plugin_timeout': TIMEOUT_DEFAULT, 'cmd_timeout': TIMEOUT_DEFAULT, 'profiles': [], 'since': None, 'verify': False, 'allow_system_changes': False, 'usernames': [], 'upload': False, 'upload_url': None, 'upload_directory': None, 'upload_user': None, 'upload_pass': None, 'upload_method': 'auto', 'upload_no_ssl_verify': False, 'upload_protocol': 'auto', 'upload_s3_endpoint': None, 'upload_s3_region': None, 'upload_s3_bucket': None, 'upload_s3_access_key': None, 'upload_s3_secret_key': None, 'upload_s3_object_prefix': None, 'add_preset': '', 'del_preset': '' } def __init__(self, parser, args, cmdline): super().__init__(parser, args, cmdline) self.loaded_plugins = [] self.skipped_plugins = [] self.all_options = [] self.env_vars = set() self._args = args self.sysroot = "/" self.estimated_plugsizes = {} self.print_header() self._set_debug() self._is_root = self.policy.is_root() # add a manifest section for report self.report_md = self.manifest.components.add_section('report') self._set_directories() msg = "default" self.sysroot = self.policy.sysroot # set alternate system root directory if self.opts.sysroot: msg = "cmdline" elif self.policy.in_container() and self.sysroot != os.sep: msg = "policy" self.soslog.debug(f"set sysroot to '{self.sysroot}' ({msg})") if self.opts.chroot not in chroot_modes: self.soslog.error(f"invalid chroot mode: {self.opts.chroot}") logging.shutdown() self.tempfile_util.clean() self._exit(1) self._check_container_runtime() self._get_namespaces() self._get_hardware_devices() @classmethod def add_parser_options(cls, parser): report_grp = parser.add_argument_group( 'Report Options', 'These options control how report collects data' ) report_grp.add_argument("-a", "--alloptions", action="store_true", dest="alloptions", default=False, help="enable all options for loaded plugins") report_grp.add_argument("--all-logs", action="store_true", dest="all_logs", default=False, help="collect all available logs regardless " "of size") report_grp.add_argument("--since", action="store", dest="since", default=None, type=_format_since, help="Escapes archived files older than date. " "This will also affect --all-logs. " "Format: YYYYMMDD[HHMMSS]") report_grp.add_argument("--build", action="store_true", dest="build", default=False, help="preserve the temporary directory and do " "not package results") report_grp.add_argument("--case-id", action="store", dest="case_id", help="specify case identifier") report_grp.add_argument("-c", "--chroot", action="store", dest="chroot", default='auto', help="chroot executed commands to SYSROOT " "[auto, always, never] (default=auto)") report_grp.add_argument("--container-runtime", default="auto", help="Default container runtime to use for " "collections. 'auto' for policy control.") report_grp.add_argument("--desc", "--description", type=str, action="store", default="", help="Description for a new preset",) report_grp.add_argument("--dry-run", action="store_true", help="Run plugins but do not collect data") report_grp.add_argument("--estimate-only", action="store_true", help="Approximate disk space requirements for " "a real sos run; disables --clean and " "--collect, sets --threads=1 and " "--no-postproc") report_grp.add_argument("--experimental", action="store_true", dest="experimental", default=False, help="enable experimental plugins") report_grp.add_argument("-e", "--enable-plugins", action="extend", dest="enable_plugins", type=str, help="enable these plugins", default=[]) report_grp.add_argument("--journal-size", type=int, default=100, dest="journal_size", help="limit the size of collected journals " "in MiB") report_grp.add_argument("-k", "--plugin-option", "--plugopts", action="extend", dest="plugopts", type=str, help="plugin options in plugname.option=value " "format (see -l)", default=[]) report_grp.add_argument("--label", "--name", action="store", dest="label", help="specify an additional report label") report_grp.add_argument("-l", "--list-plugins", action="store_true", dest="list_plugins", default=False, help="list plugins and available plugin " "options") report_grp.add_argument("--list-presets", action="store_true", help="display a list of available presets") report_grp.add_argument("--list-profiles", action="store_true", dest="list_profiles", default=False, help="display a list of available profiles and" " plugins that they include") report_grp.add_argument("--log-size", action="store", dest="log_size", type=int, default=25, help="limit the size of collected logs " "(not journals) in MiB") report_grp.add_argument("--low-priority", action="store_true", default=False, help="generate report with low system priority" ) report_grp.add_argument("--namespaces", default=None, help="limit number of namespaces to collect " "output for - 0 means unlimited") report_grp.add_argument("-n", "--skip-plugins", action="extend", dest="skip_plugins", type=str, help="disable these plugins", default=[]) report_grp.add_argument("--no-report", action="store_true", dest="no_report", default=False, help="disable plaintext/HTML reporting") report_grp.add_argument("--no-env-vars", action="store_true", dest="no_env_vars", default=False, help="Do not collect environment variables") report_grp.add_argument("--no-postproc", default=False, dest="no_postproc", action="store_true", help="Disable all post-processing") report_grp.add_argument("--note", type=str, action="store", default="", help="Behaviour notes for new preset") report_grp.add_argument("-o", "--only-plugins", action="extend", dest="only_plugins", type=str, help="enable these plugins only", default=[]) report_grp.add_argument("--preset", action="store", type=str, help="A preset identifier", default="auto") report_grp.add_argument("--plugin-timeout", default=None, help="set a timeout for all plugins") report_grp.add_argument("--cmd-timeout", default=None, help="set a command timeout for all plugins") report_grp.add_argument("-p", "--profile", "--profiles", action="extend", dest="profiles", type=str, default=[], help="enable plugins used by the given " "profiles") report_grp.add_argument('--skip-commands', default=[], action='extend', dest='skip_commands', help="do not execute these commands") report_grp.add_argument('--skip-files', default=[], action='extend', dest='skip_files', help="do not collect these files") report_grp.add_argument("--verify", action="store_true", dest="verify", default=False, help="perform data verification during " "collection") report_grp.add_argument("--allow-system-changes", action="store_true", dest="allow_system_changes", default=False, help="Run commands even if they can change the" " system (e.g. load kernel modules)") report_grp.add_argument("--upload", action="store_true", default=False, help="Upload archive to a policy-default " "location") report_grp.add_argument("--upload-url", default=None, help="Upload the archive to specified server") report_grp.add_argument("--upload-directory", default=None, help="Specify upload directory for archive") report_grp.add_argument("--upload-user", default=None, help="Username to authenticate to server with") report_grp.add_argument("--upload-pass", default=None, help="Password to authenticate to server with") report_grp.add_argument("--upload-method", default='auto', choices=['auto', 'put', 'post'], help="HTTP method to use for uploading") report_grp.add_argument("--upload-no-ssl-verify", default=False, action='store_true', help="Disable SSL verification for upload url") report_grp.add_argument("--upload-s3-endpoint", default=None, help="Endpoint to upload to for S3 bucket") report_grp.add_argument("--upload-s3-region", default=None, help="Region to upload to for S3 bucket") report_grp.add_argument("--upload-s3-bucket", default=None, help="Name of the S3 bucket to upload to") report_grp.add_argument("--upload-s3-access-key", default=None, help="Access key for the S3 bucket") report_grp.add_argument("--upload-s3-secret-key", default=None, help="Secret key for the S3 bucket") report_grp.add_argument("--upload-s3-object-prefix", default=None, help="Prefix for the S3 object/key") report_grp.add_argument("--upload-protocol", default='auto', choices=['auto', 'https', 'ftp', 'sftp', 's3'], help="Manually specify the upload protocol") # Group to make add/del preset exclusive preset_grp = report_grp.add_mutually_exclusive_group() preset_grp.add_argument("--add-preset", type=str, action="store", help="Add a new named command line preset") preset_grp.add_argument("--del-preset", type=str, action="store", help="Delete the named command line preset") # Group the cleaner options together cleaner_grp = parser.add_argument_group( 'Cleaner/Masking Options', 'These options control how data obfuscation is performed' ) cleaner_grp.add_argument('--clean', '--cleaner', '--mask', dest='clean', default=False, action='store_true', help='Obfuscate sensitive information') cleaner_grp.add_argument('--domains', dest='domains', default=[], action='extend', help='Additional domain names to obfuscate') cleaner_grp.add_argument('--disable-parsers', action='extend', default=[], dest='disable_parsers', help=('Disable specific parsers, so that ' 'those elements are not obfuscated')) cleaner_grp.add_argument('--skip-cleaning-files', '--skip-masking-files', action='extend', default=[], dest='skip_cleaning_files', help=('List of files to skip/ignore during ' 'cleaning. Globs are supported.')) cleaner_grp.add_argument('--keywords', action='extend', default=[], dest='keywords', help='List of keywords to obfuscate') cleaner_grp.add_argument('--keyword-file', default=None, dest='keyword_file', help='Provide a file a keywords to obfuscate') cleaner_grp.add_argument('--no-update', action='store_true', default=False, dest='no_update', help='Do not update the default cleaner map') cleaner_grp.add_argument('--map-file', dest='map_file', default='/etc/sos/cleaner/default_mapping', help=('Provide a previously generated mapping' ' file for obfuscation')) cleaner_grp.add_argument('--keep-binary-files', default=False, action='store_true', dest='keep_binary_files', help='Keep unprocessable binary files in the ' 'archive instead of removing them') cleaner_grp.add_argument('--usernames', dest='usernames', default=[], action='extend', help='List of usernames to obfuscate') @classmethod def display_help(cls, section): section.set_title('SoS Report Detailed Help') section.add_text( 'The report command is the most common use case for SoS, and aims ' 'to collect relevant diagnostic and troubleshooting data to assist' ' with issue analysis without actively performing that analysis on' ' the system while it is in use.' ) section.add_text( 'Additionally, sos report archives can be used for ongoing ' 'inspection for pre-emptive issue monitoring, such as that done ' 'by the Insights project.' ) section.add_text( 'The typical result of an execution of \'sos report\' is a tarball' ' that contains troubleshooting command output, copies of config ' 'files, and copies of relevant sections of the host filesystem. ' 'Root privileges are required for collections.' ) psec = section.add_section(title='How Collections Are Determined') psec.add_text( 'SoS report performs its collections by way of \'plugins\' that ' 'individually specify what files to copy and what commands to run.' ' Plugins typically map to specific components or software ' 'packages.' ) psec.add_text( 'Plugins may specify different collections on different distribu' 'tions, and some plugins may only be for specific distributions. ' 'Distributions are represented within SoS by \'policies\' and may ' 'influence how other SoS commands or options function. For ' 'example policies can alter where the --upload option defaults ' 'to or functions.' ) ssec = section.add_section(title='See Also') ssec.add_text( "For information on available options for report, see " f"{bold('sos report --help')} and {bold('man sos-report')}" ) ssec.add_text(f"The following {bold('sos help')} sections may be of " "interest:\n") help_lines = { 'report.plugins': 'Information on the plugin design of sos', 'report.plugins.$plugin': 'Information on a specific $plugin', 'policies': 'How sos operates on different distributions' } helpln = '' for ln, value in help_lines.items(): ssec.add_text(f"\t{ln:<36}{value}", newline=False) ssec.add_text(helpln) def print_header(self): print(f"\n{_(f'sos report (version {__version__})')}\n") def _get_hardware_devices(self): self.devices = { 'storage': { 'block': self._get_block_devs(), 'fibre': self._get_fibre_devs() }, 'network': self._get_network_devs(), 'namespaced_network': self._get_network_namespace_devices(), 'fstype': self._get_devices_by_fstype() } def _check_container_runtime(self): """Check the loaded container runtimes, and the policy default runtime (if set), against any requested --container-runtime value. This can be useful for systems that have multiple runtimes, such as RHCOS, but do not have a clearly defined 'default' (or one that is determined based entirely on configuration). """ if self.opts.container_runtime != 'auto': crun = self.opts.container_runtime.lower() if crun in ['none', 'off', 'diabled']: self.policy.runtimes = {} self.soslog.info( "Disabled all container runtimes per user option." ) elif not self.policy.runtimes: msg = ("WARNING: No container runtimes are active, ignoring " f"option to set default runtime to '{crun}'\n") self.soslog.warning(msg) elif crun not in self.policy.runtimes.keys(): valid = ', '.join(p for p in self.policy.runtimes.keys() if p != 'default') raise Exception(f"Cannot use container runtime '{crun}': no " "such runtime detected. Available runtimes: " f"{valid}") else: self.policy.runtimes['default'] = self.policy.runtimes[crun] self.soslog.info( "Set default container runtime to " f"'{self.policy.runtimes['default'].name}'" ) def _get_fibre_devs(self): """Enumerate a list of fibrechannel devices on this system so that plugins can iterate over them These devices are used by add_device_cmd() in the Plugin class. """ try: devs = [] devdirs = [ 'fc_host', 'fc_transport', 'fc_remote_ports', 'fc_vports' ] for devdir in devdirs: if os.path.isdir(f"/sys/class/{devdir}"): devs.extend(glob.glob(f"/sys/class/{devdir}/*")) return devs except Exception as err: self.soslog.error(f"Could not get fibre device list: {err}") return [] def _get_block_devs(self): """Enumerate a list of block devices on this system so that plugins can iterate over them These devices are used by add_device_cmd() in the Plugin class. """ try: device_list = [f"/dev/{d}" for d in os.listdir('/sys/block')] loop_devices = sos_get_command_output('losetup --all --noheadings') real_loop_devices = [] if loop_devices['status'] == 0: for loop_dev in loop_devices['output'].splitlines(): loop_device = loop_dev.split()[0].replace(':', '') real_loop_devices.append(loop_device) ghost_loop_devs = [dev for dev in device_list if dev.startswith("loop") if dev not in real_loop_devices] dev_list = list(set(device_list) - set(ghost_loop_devs)) return dev_list except Exception as err: self.soslog.error(f"Could not get block device list: {err}") return [] def _get_namespaces(self): self.namespaces = { 'network': self._get_network_namespaces() } def _get_network_devs(self): """Helper to encapsulate network devices probed by sos. Rather than probing lists of distinct device types like we do for storage, we can do some introspection of device enumeration where a single interface may have multiple device types. E.G an 'ethernet' device could also be a bond, and that is pertinent information for device iteration. :returns: A collection of enumerated devices sorted by device type :rtype: ``dict`` with keys being device types """ _devs = { 'ethernet': [], 'bridge': [], 'team': [], 'bond': [] } try: if is_executable('nmcli', sysroot=self.opts.sysroot): _devs.update(self._get_nmcli_devs()) # if nmcli failed for some reason, fallback if not _devs['ethernet']: self.soslog.debug( 'Network devices not enumerated by nmcli. Will attempt to ' 'manually compile list of devices.' ) _devs.update(self._get_eth_devs()) _devs['bridge'] = self._get_bridge_devs() except Exception as err: self.soslog.warning(f"Could not enumerate network devices: {err}") return _devs def _get_network_namespace_devices(self): """Enumerate the network devices that exist within each network namespace that exists on the system """ _nmdevs = {} for nmsp in self.namespaces['network']: _nmdevs[nmsp] = { 'ethernet': self._get_eth_devs(nmsp) } return _nmdevs def _get_nmcli_devs(self): """Use nmcli, if available, to enumerate network devices. From this output, manually grok together lists of devices. """ _devs = {} try: _ndevs = sos_get_command_output('nmcli --fields DEVICE,TYPE dev') if _ndevs['status'] == 0: for dev in _ndevs['output'].splitlines()[1:]: dname, dtype = dev.split() if dtype not in _devs: _devs[dtype] = [dname] else: _devs[dtype].append(dname) _devs['ethernet'].append(dname) _devs['ethernet'] = list(set(_devs['ethernet'])) except Exception as err: self.soslog.debug(f"Could not parse nmcli devices: {err}") return _devs def _get_eth_devs(self, namespace=None): """Enumerate a list of ethernet network devices so that plugins can reliably iterate over the same set of devices without doing piecemeal discovery. These devices are used by `add_device_cmd()` when `devices` includes "ethernet" or "network". :param namespace: Inspect this existing network namespace, if provided :type namespace: ``str`` :returns: All valid ethernet devices found, potentially within a given namespace :rtype: ``list`` """ filt_devs = ['bonding_masters'] _eth_devs = [] if not namespace: try: # Override checking sysroot here, as network devices will not # be under the sysroot in live environments or in containers # that are correctly setup to collect from the host _eth_devs = [ dev for dev in listdir('/sys/class/net', None) if dev not in filt_devs ] except Exception as err: self.soslog.warning( f'Failed to manually determine network devices: {err}' ) else: try: _nscmd = f"ip netns exec {namespace} ls /sys/class/net" _nsout = sos_get_command_output(_nscmd) if _nsout['status'] == 0: for _nseth in _nsout['output'].split(): if _nseth not in filt_devs: _eth_devs.append(_nseth) except Exception as err: self.soslog.warning( f"Could not determine network namespace '{namespace}' " f"devices: {err}" ) return { 'ethernet': _eth_devs, 'bond': [bd for bd in _eth_devs if bd.startswith('bond')], 'tun': [td for td in _eth_devs if td.startswith('tun')] } def _get_bridge_devs(self): """Enumerate a list of bridge devices so that plugins can reliably iterate over the same set of bridges. These devices are used by `add_device_cmd()` when `devices` includes "bridge" or "network". """ _bridges = [] try: _bout = sos_get_command_output('brctl show', timeout=15) except Exception as err: self.soslog.warning(f"Unable to enumerate bridge devices: {err}") if _bout['status'] == 0: for _bline in _bout['output'].splitlines()[1:]: try: _bridges.append(_bline.split()[0]) except Exception as err: self.soslog.info( f"Could not parse device from line '{_bline}': {err}" ) return _bridges def _get_network_namespaces(self): """Enumerate a list of network namespaces on this system so that plugins can iterate over them Note that stderr is not collected, so no handling of error lines. """ out_ns = [] ip_netns = sos_get_command_output("ip netns") if ip_netns['status'] == 0: for line in ip_netns['output'].splitlines(): if line.isspace() or line[:1].isspace(): continue out_ns.append(line.partition(' ')[0]) return out_ns def _get_devices_by_fstype(self): _dev_fstypes = {} _devs = sos_get_command_output("lsblk -snrpo FSTYPE,NAME") if _devs['status'] != 0: return _dev_fstypes for line in (_devs['output'].splitlines()): helper = line.strip().split() if len(helper) == 1: helper.insert(0, 'unknown') if "ext" in helper[0]: helper[0] = 'ext4' _dev_fstypes.setdefault(helper[0], []) _dev_fstypes[helper[0]].append(helper[1]) return _dev_fstypes def get_commons(self): return { 'cmddir': self.cmddir, 'logdir': self.logdir, 'rptdir': self.rptdir, 'tmpdir': self.tmpdir, 'soslog': self.soslog, 'policy': self.policy, 'sysroot': self.sysroot, 'verbosity': self.opts.verbosity, 'cmdlineopts': self.opts, 'devices': self.devices, 'namespaces': self.namespaces } def get_temp_file(self): return self.tempfile_util.new() def _make_archive_paths(self): self.archive.makedirs(self.cmddir, 0o755) self.archive.makedirs(self.logdir, 0o755) self.archive.makedirs(self.rptdir, 0o755) def _set_directories(self): self.cmddir = 'sos_commands' self.logdir = 'sos_logs' self.rptdir = 'sos_reports' def _set_debug(self): if self.opts.debug: sys.excepthook = self._exception self.raise_plugins = True else: self.raise_plugins = False @staticmethod def _exception(etype, eval_, etrace): """ Wrap exception in debugger if not in tty """ if hasattr(sys, 'ps1') or not sys.stderr.isatty(): # we are in interactive mode or we don't have a tty-like # device, so we call the default hook sys.__excepthook__(etype, eval_, etrace) else: # we are NOT in interactive mode, print the exception... traceback.print_exception(etype, eval_, etrace, limit=2, file=sys.stdout) print() # ...then start the debugger in post-mortem mode. pdb.pm() def handle_exception(self, plugname=None, func=None): if self.raise_plugins or self.exit_process: # retrieve exception info for the current thread and stack. (etype, val, tb) = sys.exc_info() # we are NOT in interactive mode, print the exception... traceback.print_exception(etype, val, tb, file=sys.stdout) print() # ...then start the debugger in post-mortem mode. pdb.post_mortem(tb) if plugname and func: self._log_plugin_exception(plugname, func) def _add_sos_logs(self): # Make sure the log files are added before we remove the log # handlers. This prevents "No handlers could be found.." messages # from leaking to the console when running in --quiet mode when # Archive classes attempt to acess the log API. if getattr(self, "sos_log_file", None): self.archive.add_file(self.sos_log_file, dest=os.path.join('sos_logs', 'sos.log')) if getattr(self, "sos_ui_log_file", None): self.archive.add_file(self.sos_ui_log_file, dest=os.path.join('sos_logs', 'ui.log')) def _is_in_profile(self, plugin_class): only_plugins = self.opts.only_plugins if not self.opts.profiles: return True if not hasattr(plugin_class, "profiles"): return False if only_plugins and not self._is_not_specified(plugin_class.name()): return True return any(p in self.opts.profiles for p in plugin_class.profiles) def _is_skipped(self, plugin_name): return plugin_name in self.opts.skip_plugins def _is_inactive(self, plugin_name, pluginClass): return (not pluginClass(self.get_commons()).check_enabled() and plugin_name not in self.opts.enable_plugins and plugin_name not in self.opts.only_plugins) def _is_not_default(self, plugin_name, pluginClass): return (not pluginClass(self.get_commons()).default_enabled() and plugin_name not in self.opts.enable_plugins and plugin_name not in self.opts.only_plugins) def _is_not_specified(self, plugin_name): return (self.opts.only_plugins and plugin_name not in self.opts.only_plugins) def _skip(self, plugin_class, reason="unknown"): self.skipped_plugins.append(( plugin_class.name(), plugin_class(self.get_commons()), reason )) def _load(self, plugin_class): self.loaded_plugins.append(( plugin_class.name(), plugin_class(self.get_commons()) )) def load_plugins(self): import_plugin = sos.report.plugins.import_plugin helper = ImporterHelper(sos.report.plugins) plugins = helper.get_modules() self.plugin_names = [] self.profiles = set() using_profiles = len(self.opts.profiles) policy_classes = self.policy.valid_subclasses extra_classes = [] if self.opts.experimental: extra_classes.append(sos.report.plugins.ExperimentalPlugin) valid_plugin_classes = tuple(policy_classes + extra_classes) validate_plugin = self.policy.validate_plugin remaining_profiles = list(self.opts.profiles) # validate and load plugins for plug in plugins: plugbase, __ = os.path.splitext(plug) try: plugin_classes = import_plugin(plugbase, valid_plugin_classes) if not plugin_classes: # no valid plugin classes for this policy continue plugin_class = self.policy.match_plugin(plugin_classes) if not validate_plugin(plugin_class, experimental=self.opts.experimental): self.soslog.warning( _("plugin %s does not validate, skipping") % plug) if self.opts.verbosity > 0: self._skip(plugin_class, _("does not validate")) continue # plug-in is valid, let's decide whether run it or not self.plugin_names.append(plugbase) in_profile = self._is_in_profile(plugin_class) if not in_profile: self._skip(plugin_class, _("excluded")) continue if self._is_skipped(plugbase): self._skip(plugin_class, _("skipped")) continue if self._is_inactive(plugbase, plugin_class): self._skip(plugin_class, _("inactive")) continue if self._is_not_default(plugbase, plugin_class): self._skip(plugin_class, _("optional")) continue # only add the plugin's profiles once we know it is usable if hasattr(plugin_class, "profiles"): self.profiles.update(plugin_class.profiles) # true when the null (empty) profile is active default_profile = not using_profiles and in_profile if self._is_not_specified(plugbase) and default_profile: self._skip(plugin_class, _("not specified")) continue for i in plugin_class.profiles: if i in remaining_profiles: remaining_profiles.remove(i) self._load(plugin_class) except Exception as e: self.soslog.warning(_("plugin %s does not install, " "skipping: %s") % (plug, e)) self.handle_exception() if len(remaining_profiles) > 0: self.soslog.error(_("Unknown or inactive profile(s) provided:" " %s") % ", ".join(remaining_profiles)) self.list_profiles() self._exit(1) def _set_all_options(self): if self.opts.alloptions: for __, plug in self.loaded_plugins: for opt in plug.options.values(): if bool in opt.val_type: opt.value = True def _set_tunables(self): if self.opts.plugopts: opts = {} for opt in self.opts.plugopts: try: opt, val = opt.split("=") except ValueError: val = True if isinstance(val, str): val = val.lower() if val in ["on", "enable", "enabled", "true", "yes"]: val = True elif val in ["off", "disable", "disabled", "false", "no"]: val = False else: # try to convert string "val" to int() try: val = int(val) except ValueError: # not a number to convert back to int from argparse pass try: plug, opt = opt.split(".") except ValueError: plug = opt opt = True try: opts[plug] except KeyError: opts[plug] = {} opts[plug][opt] = val for plugname, plug in self.loaded_plugins: if plugname in opts: for opt in opts[plugname]: if opt not in plug.options: self.soslog.error(f'no such option "{opt}" for ' f'plugin ({plugname})') self._exit(1) try: plug.options[opt].set_value(opts[plugname][opt]) self.soslog.debug( f"Set {plugname} plugin option to " f"{plug.options[opt]}") except Exception as err: self.soslog.error(err) self._exit(1) del opts[plugname] for plugname in opts.keys(): self.soslog.error('WARNING: unable to set option for disabled ' f'or non-existing plugin ({plugname}).') # in case we printed warnings above, visually intend them from # subsequent header text if opts.keys(): self.soslog.error('') def _check_for_unknown_plugins(self): import itertools for plugin in itertools.chain(self.opts.only_plugins, self.opts.enable_plugins): plugin_name = plugin.split(".")[0] if plugin_name not in self.plugin_names: self.soslog.fatal(f'a non-existing plugin ({plugin_name}) was ' 'specified in the command line.') self._exit(1) for plugin in self.opts.skip_plugins: if plugin not in self.plugin_names: self.soslog.warning( f"Requested to skip non-existing plugin '{plugin}'." ) def _set_plugin_options(self): for __, plugin in self.loaded_plugins: for opt in plugin.options: self.all_options.append(plugin.options[opt]) def _set_estimate_only(self): # set estimate-only mode by enforcing some options settings # and return a corresponding log messages string msg = "\nEstimate-only mode enabled" ext_msg = [] if self.opts.threads > 1: ext_msg += [f"--threads={self.opts.threads} overriden to 1", ] self.opts.threads = 1 if not self.opts.build: ext_msg += ["--build enabled", ] self.opts.build = True if not self.opts.no_postproc: ext_msg += ["--no-postproc enabled", ] self.opts.no_postproc = True if self.opts.clean: ext_msg += ["--clean disabled", ] self.opts.clean = False if self.opts.upload: ext_msg += ["--upload* options disabled", ] self.opts.upload = False if ext_msg: msg += ", which overrides some options:\n " + "\n ".join(ext_msg) else: msg += "." msg += "\n\n" return msg def _report_profiles_and_plugins(self): self.ui_log.info("") if self.loaded_plugins: self.ui_log.info(f" {len(self.profiles)} profiles, " f"{len(self.loaded_plugins)} plugins") else: # no valid plugins for this profile self.ui_log.info(f" {len(self.profiles)} profiles") self.ui_log.info("") def list_plugins(self): if not self.loaded_plugins and not self.skipped_plugins: self.soslog.fatal(_("no valid plugins found")) return if self.loaded_plugins: self.ui_log.info(_("The following plugins are currently enabled:")) self.ui_log.info("") for (plugname, plug) in self.loaded_plugins: self.ui_log.info(f"{plugname:<20} {plug.get_description()}") else: self.ui_log.info(_("No plugin enabled.")) self.ui_log.info("") if self.skipped_plugins: self.ui_log.info(_("The following plugins are currently " "disabled:")) self.ui_log.info("") for (plugname, plugclass, reason) in self.skipped_plugins: self.ui_log.info(f"{plugname:<20} {reason:<14} " f"{plugclass.get_description()}") self.ui_log.info("") if self.all_options: self.ui_log.info(_("The following options are available for ALL " "plugins:")) _defaults = self.loaded_plugins[0][1].get_default_plugin_opts() for _opt in _defaults: opt = _defaults[_opt] val = opt.value if opt.value == -1: if _opt == 'timeout': val = self.opts.plugin_timeout or TIMEOUT_DEFAULT elif _opt == 'cmd-timeout': val = self.opts.cmd_timeout or TIMEOUT_DEFAULT else: val = TIMEOUT_DEFAULT if opt.name == 'postproc': val = not self.opts.no_postproc self.ui_log.info(f"{opt.name:<25} {val:<15} {opt.desc}") self.ui_log.info("") self.ui_log.info(_("The following plugin options are available:")) for opt in self.all_options: if opt.name in ('timeout', 'postproc', 'cmd-timeout'): if opt.value == opt.default: continue # format option value based on its type (int or bool) if isinstance(opt.value, bool): if opt.value is True: tmpopt = "on" else: tmpopt = "off" else: tmpopt = opt.value if tmpopt is None: tmpopt = 0 self.ui_log.info(f" {f'{opt.plugin}.{opt.name}':<25} " f"{tmpopt:<15} {opt.desc}") else: self.ui_log.info(_("No plugin options available.")) self.ui_log.info("") profiles = list(self.profiles) profiles.sort() lines = _format_list("Profiles: ", profiles, indent=True) for line in lines: self.ui_log.info(f" {line}") self._report_profiles_and_plugins() def list_profiles(self): if not self.profiles: self.soslog.fatal(_("no valid profiles found")) return self.ui_log.info(_("The following profiles are available:")) self.ui_log.info("") def _has_prof(c): return hasattr(c, "profiles") profiles = list(self.profiles) profiles.sort() for profile in profiles: plugins = [] for name, plugin in self.loaded_plugins: if _has_prof(plugin) and profile in plugin.profiles: plugins.append(name) lines = _format_list(f"{profile:<15}", plugins, indent=True) for line in lines: self.ui_log.info(f" {line}") self._report_profiles_and_plugins() def list_presets(self): if not self.policy.presets: self.soslog.fatal(_("no valid presets found")) return self.ui_log.info(_("The following presets are available:")) self.ui_log.info("") for preset in self.policy.presets.keys(): if not preset: continue preset = self.policy.find_preset(preset) self.ui_log.info(f"name: {preset.name:>14}") self.ui_log.info(f"description: {preset.desc:>14}") if preset.note: self.ui_log.info(f"note: {preset.note:>14}") if self.opts.verbosity > 0: args = preset.opts.to_args() options_str = f"{'options:':>14}" lines = _format_list(options_str, args, indent=True, sep=' ') for line in lines: self.ui_log.info(line) self.ui_log.info("") def add_preset(self, name, desc="", note=""): """Add a new command line preset for the current options with the specified name. :param name: the name of the new preset :returns: True on success or False otherwise """ policy = self.policy if policy.find_preset(name): self.ui_log.error(f"A preset named '{name}' already exists") return False desc = desc or self.opts.desc note = note or self.opts.note try: policy.add_preset(name=name, desc=desc, note=note, opts=self.opts) except Exception as e: self.ui_log.error(f"Could not add preset: {e}") return False # Filter --add-preset from arguments list arg_index = self.cmdline.index("--add-preset") args = self.cmdline[0:arg_index] + self.cmdline[arg_index + 2:] self.ui_log.info( f"Added preset '{name}' with options {' '.join(args)}\n") return True def del_preset(self, name): """Delete a named command line preset. :param name: the name of the preset to delete :returns: True on success or False otherwise """ policy = self.policy if not policy.find_preset(name): self.ui_log.error(f"Preset '{name}' not found") return False try: policy.del_preset(name=name) except Exception as e: self.ui_log.error(str(e) + "\n") return False self.ui_log.info(f"Deleted preset '{name}'\n") return True def batch(self): msg = self.policy.get_msg() if self.opts.estimate_only: msg += self._set_estimate_only() if self.opts.batch: self.ui_log.info(msg) else: msg += _("Press ENTER to continue, or CTRL-C to quit.\n") try: input(msg) except KeyboardInterrupt: self.ui_log.error("Exiting on user cancel") self._exit(130) except Exception as e: self._exit(1, e) def _log_plugin_exception(self, plugin, method): trace = traceback.format_exc() msg = "caught exception in plugin method" plugin_err_log = f"{plugin}-plugin-errors.txt" logpath = os.path.join(self.logdir, plugin_err_log) self.soslog.error(f'{msg} "{plugin}.{method}()"') self.soslog.error(f'writing traceback to {logpath}') self.archive.add_string(f"{trace}\n", logpath, mode='a') def prework(self): self.policy.pre_work() try: self.ui_log.info(_(" Setting up archive ...")) self.setup_archive() self._make_archive_paths() return except (OSError, IOError) as e: # we must not use the logging subsystem here as it is potentially # in an inconsistent or unreliable state (e.g. an EROFS for the # file system containing our temporary log files). if e.errno in fatal_fs_errors: print("") print(f" {e.strerror} while setting up archive") print("") else: print(f"Error setting up archive: {e}") raise except Exception as e: self.ui_log.error("") self.ui_log.error(" Unexpected exception setting up archive:") traceback.print_exc() self.ui_log.error(e) self._exit(1) def setup(self): self.ui_log.info(_(" Setting up plugins ...")) for plugname, plug in self.loaded_plugins: try: self.report_md.plugins.add_section(plugname) plug.set_plugin_manifest(getattr(self.report_md.plugins, plugname)) start = datetime.now() plug.manifest.add_field('setup_start', start) plug.archive = self.archive plug.add_default_collections() plug.setup() self.env_vars.update(plug._env_vars) if self.opts.verify: plug.setup_verify() end = datetime.now() plug.manifest.add_field('setup_end', end) plug.manifest.add_field('setup_time', end - start) except KeyboardInterrupt: raise KeyboardInterrupt except (OSError, IOError) as e: if e.errno in fatal_fs_errors: self.ui_log.error("") self.ui_log.error( f" {e.strerror} while setting up plugins") self.ui_log.error("") self._exit(1) self.handle_exception(plugname, "setup") except Exception: self.handle_exception(plugname, "setup") def version(self): """Fetch version information from all plugins and store in the report version file""" versions = [] versions.append(f"sos report: {__version__}") self.archive.add_string(content="\n".join(versions), dest='version.txt') def collect(self): self.ui_log.info(_(" Running plugins. Please wait ...")) self.ui_log.info("") plugruncount = 0 self.pluglist = [] self.running_plugs = [] for i in self.loaded_plugins: plugruncount += 1 self.pluglist.append((plugruncount, i[0])) try: results = [] with ThreadPoolExecutor(self.opts.threads) as executor: results = executor.map(self._collect_plugin, list(self.pluglist)) for res in results: if not res: self.soslog.debug(f"Unexpected plugin task result: {res}") self.ui_log.info("") except KeyboardInterrupt: # We may not be at a newline when the user issues Ctrl-C self.ui_log.error("\nExiting on user cancel\n") os._exit(1) def _collect_plugin(self, plugin): """Wraps the collect_plugin() method so we can apply a timeout against the plugin as a whole""" with ThreadPoolExecutor(1) as pool: try: _plug = self.loaded_plugins[plugin[0]-1][1] t = pool.submit(self.collect_plugin, plugin) # Re-type int 0 to NoneType, as otherwise result() will treat # it as a literal 0-second timeout timeout = _plug.timeout or None start = datetime.now() _plug.manifest.add_field('start_time', start) t.result(timeout=timeout) end = datetime.now() _plug.manifest.add_field('end_time', end) _plug.manifest.add_field('run_time', end - start) except FuturesTimeoutError: msg = f"Plugin {plugin[1]} timed out" # log to ui_log.error to show the user, log to soslog.info # so that someone investigating the sos execution has it all # in one place, but without double notifying the user. self.ui_log.error(f"\n {msg}\n") self.soslog.info(msg) self.running_plugs.remove(plugin[1]) self.loaded_plugins[plugin[0]-1][1].set_timeout_hit() pool.shutdown(wait=True) pool._threads.clear() if self.opts.estimate_only: # call "du -s -B1" for the tmp dir to get the disk usage of the # data collected by the plugin - if the command fails, count with 0 tmpdir = self.archive.get_tmp_dir() try: du = sos_get_command_output(f'du -sB1 {tmpdir}') self.estimated_plugsizes[plugin[1]] = \ int(du['output'].split()[0]) except Exception: self.estimated_plugsizes[plugin[1]] = 0 # remove whole tmp_dir content - including "sos_commands" and # similar dirs that will be re-created on demand by next plugin # if needed; it is less error-prone approach than skipping # deletion of some dirs but deleting their content for f in os.listdir(tmpdir): f = os.path.join(tmpdir, f) if os.path.isdir(f) and not os.path.islink(f): rmtree(f) else: os.unlink(f) return True def collect_plugin(self, plugin): try: count, plugname = plugin plug = self.loaded_plugins[count-1][1] self.running_plugs.append(plugname) except Exception: return False numplugs = len(self.loaded_plugins) status_line = (f" Starting {f'{count}/{numplugs}':<5} {plugname:<15} " f"[Running: {' '.join(p for p in self.running_plugs)}]") self.ui_progress(status_line) try: plug.collect_plugin() # certain exceptions can cause either of these lists to no # longer contain the plugin, which will result in sos hanging # so we can't blindly call remove() on these two. try: self.pluglist.remove(plugin) except ValueError: self.soslog.debug( f"Could not remove {plugin} from plugin list, ignoring..." ) try: self.running_plugs.remove(plugname) except ValueError: self.soslog.debug( f"Could not remove {plugin} from running plugin list, " f"ignoring..." ) status = '' if (len(self.pluglist) <= int(self.opts.threads) and self.running_plugs): status = (f" Finishing plugins {' ':<12} [Running: " f"{' '.join(p for p in self.running_plugs)}]") if not self.running_plugs and not self.pluglist: status = "\n Finished running plugins" if status: self.ui_progress(status) except SoSTimeoutError: # we already log and handle the plugin timeout in the nested thread # pool this is running in, so don't do anything here. pass except (OSError, IOError) as e: if e.errno in fatal_fs_errors: self.ui_log.error( f"\n {e.strerror} while collecting plugin data") self.ui_log.error( f" Data collected still available at {self.tmpdir}\n") os._exit(1) self.handle_exception(plugname, "collect") except Exception: self.handle_exception(plugname, "collect") return None def ui_progress(self, status_line): if self.opts.verbosity == 0 and not self.opts.batch: status_line = f"\r{status_line.ljust(90)}" else: status_line = f"{status_line}\n" if not self.opts.quiet: sys.stdout.write(status_line) sys.stdout.flush() def collect_env_vars(self): if not self.env_vars: return env = '\n'.join([ f"{name}={val}" for (name, val) in [(name, f'{os.environ.get(name)}') for name in self.env_vars if os.environ.get(name) is not None] ]) + '\n' self.archive.add_string(env, 'environment') def generate_reports(self): report = Report() # generate report content for plugname, plug in self.loaded_plugins: section = Section(name=plugname) for alert in plug.alerts: section.add(Alert(alert)) if plug.custom_text: section.add(Note(plug.custom_text)) for f in plug.copied_files: section.add(CopiedFile(name=f['srcpath'], href=".." + f['dstpath'])) for cmd in plug.executed_commands: section.add(Command(name=cmd['cmd'], return_code=0, href=os.path.join( "..", self.get_commons()['cmddir'], cmd['file'] ))) for __, f, __ in plug.copy_strings: section.add(CreatedFile(name=f, href=os.path.join("..", f))) report.add(section) # print it in text, JSON and HTML formats formatlist = ( (PlainTextReport, "sos.txt", "text"), (JSONReport, "sos.json", "JSON"), (HTMLReport, "sos.html", "HTML") ) for class_, filename, type_ in formatlist: try: fd = self.get_temp_file() output = class_(report).unicode() # safeguard against non-UTF characters output = output.encode('utf-8', 'replace').decode() fd.write(output) fd.flush() self.archive.add_file(fd, dest=os.path.join('sos_reports', filename)) except (OSError, IOError) as e: if e.errno in fatal_fs_errors: self.ui_log.error("") self.ui_log.error( f" {e.strerror} while writing {type_} report") self.ui_log.error("") self._exit(1) def postproc(self): for plugname, plug in self.loaded_plugins: try: if plug.get_option('postproc'): plug.postproc() else: self.soslog.info( f"Skipping postproc for plugin {plugname}") except (OSError, IOError) as e: if e.errno in fatal_fs_errors: self.ui_log.error("") self.ui_log.error( f" {e.strerror} while post-processing plugin data") self.ui_log.error("") self._exit(1) self.handle_exception(plugname, "postproc") except Exception: self.handle_exception(plugname, "postproc") def _create_checksum(self, archive, hash_name): if not archive: return False try: hash_size = 1024**2 # Hash 1MiB of content at a time. digest = hashlib.new(hash_name) with open(archive, 'rb') as archive_fp: while True: hashdata = archive_fp.read(hash_size) if not hashdata: break digest.update(hashdata) except Exception: self.handle_exception() return digest.hexdigest() def _write_checksum(self, archive, hash_name, checksum): # store checksum into file with open(archive + "." + hash_name, "w", encoding='utf-8') as fp: if checksum: fp.write(checksum + "\n") def final_work(self): archive = None # archive path directory = None # report directory path (--build) map_file = None # path of the map file generated for the report self.generate_manifest_tag_summary() # use this instead of self.opts.clean beyond the initial check if # cleaning was requested in case SoSCleaner fails for some reason do_clean = False if self.opts.clean: try: hook_commons = { 'policy': self.policy, 'tmpdir': self.tmpdir, 'sys_tmp': self.sys_tmp, 'options': self.opts, 'manifest': self.manifest } cleaner = SoSCleaner(in_place=True, hook_commons=hook_commons) cleaner.set_target_path(self.archive.get_archive_path()) # ignore the returned paths here map_file, _paths = cleaner.execute() do_clean = True except Exception as err: print(_(f"ERROR: Unable to obfuscate report: {err}")) self._add_sos_logs() if self.manifest is not None: self.archive.add_final_manifest_data(self.opts.compression_type) # Hide upload passwords in the log files self._obfuscate_upload_passwords() # Now, separately clean the log files that cleaner also wrote to if do_clean: _dir = os.path.join(self.tmpdir, self.archive._name) cleaner.obfuscate_file(os.path.join(_dir, 'sos_logs', 'sos.log'), short_name='sos.log') cleaner.obfuscate_file(os.path.join(_dir, 'sos_logs', 'ui.log'), short_name='ui.log') cleaner.obfuscate_file( os.path.join(_dir, 'sos_reports', 'manifest.json'), short_name='manifest.json' ) # Now, just (optionally) pack the report and print work outcome; let # print ui_log to stdout also in quiet mode. For non-quiet mode we # already added the handler if self.opts.quiet: self.add_ui_log_to_stdout() # print results in estimate mode (to include also just added manifest) if self.opts.estimate_only: from sos.utilities import get_human_readable from pathlib import Path # add sos_logs, sos_reports dirs, etc., basically everything # that remained in self.tmpdir after plugins' contents removal # that still will be moved to the sos report final directory path tmpdir_path = Path(self.tmpdir) self.estimated_plugsizes['sos_logs_reports'] = sum( f.lstat().st_size for f in tmpdir_path.glob('**/*')) _sum = get_human_readable(sum(self.estimated_plugsizes.values())) self.ui_log.info("Estimated disk space requirement for whole " f"uncompressed sos report directory: {_sum}") bigplugins = sorted(self.estimated_plugsizes.items(), key=lambda x: x[1], reverse=True)[:5] bp_out = ", ".join(f"{p}: {get_human_readable(v, precision=0)}" for p, v in bigplugins) self.ui_log.info(f"Five biggest plugins: {bp_out}") self.ui_log.info("") self.ui_log.info("Please note the estimation is relevant to the " "current options.") self.ui_log.info("Be aware that the real disk space requirements " "might be different. A rule of thumb is to " "reserve at least double the estimation.") self.ui_log.info("") # package up and compress the results if not self.opts.build: old_umask = os.umask(0o077) if not self.opts.quiet: print(_("Creating compressed archive...")) # compression could fail for a number of reasons try: if do_clean: self.archive.rename_archive_root(cleaner) archive = self.archive.finalize( self.opts.compression_type) except (OSError, IOError) as e: print("") print(_(f" {e.strerror} while finalizing archive " f"{self.archive.get_archive_path()}")) print("") if e.errno in fatal_fs_errors: self._exit(1) except Exception: if self.opts.debug: raise return False finally: os.umask(old_umask) else: if self.opts.encrypt_pass or self.opts.encrypt_key: self.ui_log.warning("\nUnable to encrypt when using --build. " "Encryption is only available for " "archives.") # move the archive root out of the private tmp directory. directory = self.archive.get_archive_path() dir_name = os.path.basename(directory) if do_clean: dir_name = cleaner.obfuscate_string(dir_name) try: final_dir = os.path.join(self.sys_tmp, dir_name) os.rename(directory, final_dir) directory = final_dir except (OSError, IOError): print(_(f"Error moving directory: {directory}")) return False checksum = None if not self.opts.build: # if creating archive file failed, report it and # skip generating checksum if not archive: print("Creating archive tarball failed.") else: try: # compute and store the archive checksum hash_name = self.policy.get_preferred_hash_name() checksum = self._create_checksum(archive, hash_name) except Exception: print(_("Error generating archive checksum after " "archive creation.\n")) return False try: self._write_checksum(archive, hash_name, checksum) except (OSError, IOError): print(_(f"Error writing checksum for file: {archive}")) # output filename is in the private tmpdir - move it to the # containing directory. base_archive = os.path.basename(archive) if do_clean: base_archive = cleaner.obfuscate_string( base_archive.replace('.tar', '-obfuscated.tar') ) final_name = os.path.join(self.sys_tmp, base_archive) # Get stat on the archive archivestat = os.stat(archive) archive_hash = archive + "." + hash_name final_hash = final_name + "." + hash_name # move the archive and checksum file try: os.rename(archive, final_name) archive = final_name except (OSError, IOError): print(_(f"Error moving archive file: {archive}")) return False # There is a race in the creation of the final checksum file: # since the archive has already been published and the checksum # file name is predictable once the archive name is known a # malicious user could attempt to create a symbolic link in # order to misdirect writes to a file of the attacker's choose. # # To mitigate this we write the checksum inside the private tmp # directory and use an atomic rename that is guaranteed to # either succeed or fail: at worst the move will fail and be # reported to the user. The correct checksum value is still # written to the terminal and nothing is written to a location # under the control of the user creating the link. try: os.rename(archive_hash, final_hash) except (OSError, IOError): print(_(f"Error moving checksum file: {archive_hash}")) self.policy.display_results(archive, directory, checksum, archivestat, map_file=map_file) else: self.policy.display_results(archive, directory, checksum, map_file=map_file) if (self.opts.upload or self.opts.upload_url or self.opts.upload_s3_endpoint): if not self.opts.build: try: self.policy.upload_archive(archive) self.ui_log.info(_("Uploaded archive successfully")) except Exception as err: self.ui_log.error(f"Upload attempt failed: {err}") else: msg = ("Unable to upload archive when using --build as no " "archive is created.") self.ui_log.error(msg) # clean up logging.shutdown() if self.tempfile_util: self.tempfile_util.clean() if self.tmpdir and os.path.isdir(self.tmpdir): rmtree(self.tmpdir) return True def verify_plugins(self): if not self.loaded_plugins: self.soslog.error(_("no valid plugins were enabled")) return False return True def add_manifest_data(self): """Add 'global' data to the manifest, that is any information that is not plugin-specific """ self.report_md.add_field('sysroot', self.sysroot) self.report_md.add_field('preset', self.preset.name if self.preset else 'unset') self.report_md.add_list('profiles', self.opts.profiles) _io_class = 'unknown' if is_executable('ionice'): _io = sos_get_command_output(f"ionice -p {os.getpid()}") if _io['status'] == 0: _io_class = _io['output'].split()[0].strip(':') self.report_md.add_section('priority') self.report_md.priority.add_field('io_class', _io_class) self.report_md.priority.add_field('niceness', os.nice(0)) self.report_md.add_section('devices') for key, value in self.devices.items(): self.report_md.devices.add_field(key, value) self.report_md.add_list('enabled_plugins', self.opts.enable_plugins) self.report_md.add_list('disabled_plugins', self.opts.skip_plugins) self.report_md.add_section('plugins') def generate_manifest_tag_summary(self): """Add a section to the manifest that contains a dict summarizing the tags that were created and assigned during this report's creation. This summary dict can be used for easier inspection of tagged items by inspection/analyzer projects such as Red Hat Insights. The format of this dict is `{tag_name: [file_list]}`. """ def compile_tags(ent, key='filepath'): for tag in ent['tags']: if not ent[key] or not tag: continue try: path = tag_summary[tag] except KeyError: path = [] path.extend( ent[key] if isinstance(ent[key], list) else [ent[key]] ) tag_summary[tag] = sorted(list(set(path))) tag_summary = {} for plug in self.report_md.plugins: for cmd in plug.commands: compile_tags(cmd) for _file in plug.files: compile_tags(_file, 'files_copied') for collection in plug.collections: compile_tags(collection) self.report_md.add_field('tag_summary', dict(sorted(tag_summary.items()))) def _merge_preset_options(self): # Log command line options msg = "[%s:%s] executing 'sos %s'" self.soslog.info(msg % (__name__, "setup", " ".join(self.cmdline))) # Log active preset defaults preset_args = self.preset.opts.to_args() msg = (f"[{__name__}:setup] using '{self.preset.name}' preset defaults" f" ({' '.join(preset_args)})") self.soslog.info(msg) # Log effective options after applying preset defaults self.soslog.info(f"[{__name__}:setup] effective options now: " f"{' '.join(self.opts.to_args())}") def execute(self): try: self.policy.set_commons(self.get_commons()) self.load_plugins() self._set_all_options() self._merge_preset_options() self._set_tunables() self._check_for_unknown_plugins() self._set_plugin_options() if self.opts.list_plugins: self.list_plugins() raise SystemExit if self.opts.list_profiles: self.list_profiles() raise SystemExit if self.opts.list_presets: self.list_presets() raise SystemExit if self.opts.add_preset: self.add_preset(self.opts.add_preset) raise SystemExit if self.opts.del_preset: self.del_preset(self.opts.del_preset) raise SystemExit # verify that at least one plug-in is enabled if not self.verify_plugins(): raise SystemExit self.batch() self.prework() self.add_manifest_data() self.setup() self.collect() if not self.opts.no_env_vars: self.collect_env_vars() if not self.opts.no_report: self.generate_reports() if not self.opts.no_postproc: self.postproc() else: self.ui_log.info("Skipping postprocessing of collected data") self.version() return self.final_work() except OSError: if self.opts.debug: raise if not os.getenv('SOS_TEST_LOGS', None) == 'keep': self.cleanup() except KeyboardInterrupt: self.ui_log.error("\nExiting on user cancel") self.cleanup() self._exit(130) except SystemExit as e: if not os.getenv('SOS_TEST_LOGS', None) == 'keep': self.cleanup() sys.exit(e.code) self._exit(1) # Never gets here. This is to fix "inconsistent-return-statements return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/0000775000175000017500000000000014660147624014567 5ustar arifarifsos-4.8.0/sos/report/plugins/navicli.py0000664000175000017500000000473714660147624016601 0ustar arifarif# Copyright (C) 2008 EMC Corporation. Keith Kearnan # Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt from sos.utilities import is_executable class Navicli(Plugin, RedHatPlugin): short_desc = 'EMC Navicli' plugin_name = 'navicli' profiles = ('storage', 'hardware') option_list = [ PluginOpt('ipaddrs', default='', val_type=str, desc='space-delimited list of CLARiiON IP addresses') ] def check_enabled(self): return is_executable("navicli") def get_navicli_config(self): """ EMC Navisphere Host Agent NAVICLI specific information - files """ self.add_copy_spec([ "/etc/Navisphere/agent.config", "/etc/Navisphere/Navimon.cfg", "/etc/Navisphere/Quietmode.cfg", "/etc/Navisphere/messages/[a-z]*", "/etc/Navisphere/log/[a-z]*" ]) def get_navicli_sp_info(self, sp_address): """ EMC Navisphere Host Agent NAVICLI specific information - CLARiiON - commands """ self.add_cmd_output([ f"navicli -h {sp_address} getall", f"navicli -h {sp_address} getsptime -spa", f"navicli -h {sp_address} getsptime -spb", f"navicli -h {sp_address} getlog", f"navicli -h {sp_address} getdisk", f"navicli -h {sp_address} getcache", f"navicli -h {sp_address} getlun", f"navicli -h {sp_address} getlun -rg -type -default -owner -crus " "-capacity", f"navicli -h {sp_address} lunmapinfo", f"navicli -h {sp_address} getcrus", f"navicli -h {sp_address} port -list -all", f"navicli -h {sp_address} storagegroup -list", f"navicli -h {sp_address} spportspeed -get", ]) def setup(self): self.get_navicli_config() for addr in set(self.get_option("ipaddrs").split()): if self.exec_cmd(f"navicli -h {addr} getsptime")['status'] == 0: self.get_navicli_sp_info(addr) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/gfs2.py0000664000175000017500000000251314660147624016003 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Gfs2(Plugin, IndependentPlugin): short_desc = 'GFS2 (Global Filesystem 2)' plugin_name = "gfs2" profiles = ("cluster", ) packages = ("gfs2-utils",) option_list = [ PluginOpt('lockdump', default=False, desc='collect lock dumps for all GFS2 filesystems') ] def setup(self): self.add_copy_spec([ "/sys/fs/gfs2/*/withdraw" ]) self.add_cmd_output([ "gfs_control ls -n", "gfs_control dump" ]) if self.get_option("gfs2lockdump"): self.add_copy_spec("/sys/kernel/debug/gfs2/*") tunegfs2_opts = '-l' mounts = '/proc/mounts' gfs2_fs_regex = r"^(/dev/\S+).+gfs2\s+" for dev in self.do_regex_find_all(gfs2_fs_regex, mounts): self.add_cmd_output(f"tunegfs2 {tunegfs2_opts} {dev}", tags="tunegfs2_l") # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/openhpi.py0000664000175000017500000000155614660147624016612 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OpenHPI(Plugin, RedHatPlugin): short_desc = 'Open Hardware Platform Interface' plugin_name = 'openhpi' profiles = ('system', 'hardware') def setup(self): self.add_copy_spec([ "/etc/openhpi/openhpi.conf", "/etc/openhpi/openhpiclient.conf" ]) def postproc(self): self.do_file_sub("/etc/openhpi/openhpi.conf", r'(\s*pass.*\s*=\s*).*', r'\1********') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/md.py0000664000175000017500000000233514660147624015544 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Md(Plugin, IndependentPlugin): short_desc = 'MD RAID subsystem' plugin_name = 'md' profiles = ('storage',) def setup(self): self.add_cmd_output("mdadm -D /dev/md*") mdadm_members = self.exec_cmd("lsblk -o NAME,FSTYPE -r") if mdadm_members['status'] == 0: for line in mdadm_members['output'].splitlines(): if 'linux_raid_member' in line: dev = line.split()[0] self.add_cmd_output(f'mdadm -E /dev/{dev}', tags="mdadm_E") self.add_copy_spec([ "/etc/mdadm.conf", "/dev/md/md-device-map", "/proc/sys/dev/raid/*", "/sys/block/md*/md*" ]) self.add_copy_spec("/proc/mdstat", tags='mdstat') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/console.py0000664000175000017500000000154514660147624016610 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class Console(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'Console and keyboard information' plugin_name = 'console' profiles = ('system',) packages = ('kbd',) def setup(self): self.add_copy_spec("/proc/consoles") self.add_cmd_output("fgconsole") self.add_cmd_output([ f"kbdinfo -C {tty} gkbled" for tty in glob("/dev/tty[0-8]") ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/validation_framework.py0000664000175000017500000000277714660147624021365 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Cedric Jeanneret # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin # Notes: # - The Validation Framework is, for now, linked to openstack and tripleo # - Since the intent is to open it to other product (we can validate anything) # this plugin has a generic name. # - The Framework is targeted at Red Hat products, at least for now. class ValidationFramework(Plugin, RedHatPlugin): short_desc = 'Logs provided by the Validation Framework' plugin_name = 'validation_framework' profiles = ('openstack', 'openstack_controller', 'openstack_compute') packages = ('tripleo-validations',) def setup(self): self.add_copy_spec('/var/log/validations/') def postproc(self): # Use a generic match in order to clean things up. # It is not expected to get any secrets in here, but we'd better # ensure it's clean. secrets = r'(".*(key|password|pass|secret|database_connection))' \ r'([":\s]+)(.*[^"])([",]+)' self.do_path_regex_sub('/var/log/validations/', secrets, r'\1\3*********\5') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/drbd.py0000664000175000017500000000167314660147624016063 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class DRDB(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'Distributed Replicated Block Device (DRBD)' plugin_name = 'drbd' profiles = ('storage',) packages = ('drbd*-utils',) def setup(self): self.add_cmd_output([ "drbd-overview", "drbdadm dump-xml", "drbdsetup status all", "drbdsetup show all" ]) self.add_copy_spec([ "/etc/drbd.conf", "/etc/drbd.d/*", "/proc/drbd" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rhcos.py0000664000175000017500000000255614660147624016267 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class RHCoreOS(Plugin, RedHatPlugin): short_desc = 'Red Hat CoreOS' plugin_name = 'rhcos' packages = ('afterburn', 'redhat-release-coreos') def setup(self): units = ['coreos-boot-edit', 'coreos-copy-firstboot-network', 'coreos-generate-iscsi-initiatorname', 'coreos-gpt-setup', 'coreos-teardown-initramfs', 'gcp-routes', 'ignition-disks', 'ignition-fetch', 'ignition-fetch-offline', 'ignition-files', 'ignition-firstboot-complete', 'ignition-mount', 'ignition-ostree-growfs', 'ignition-ostree-populate-var', 'ignition-remount-system', 'ignition-setup-user'] for unit in units: self.add_journal(unit) self.add_cmd_output( 'afterburn --cmdline --attributes /dev/stdout', timeout=60 ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sedutil.py0000664000175000017500000000331514660147624016614 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class SEDUtility(Plugin, IndependentPlugin): """ Collects information about SED drives installed on host system. This plugin will capture data using sedutil utility """ short_desc = 'Self Encrypting Drives' plugin_name = 'sedutil' profiles = ('security', 'system', 'storage', 'hardware') packages = ('sedutil',) option_list = [ PluginOpt('debug', default=False, desc='capture debug data'), ] def setup(self): sed_list = [] result = self.collect_cmd_output('sedutil-cli --scan') if self.get_option("debug"): if 0 == result['status']: # iterate over all the devices returned and # create a list of SED drives. for line in result['output'].splitlines(): if line.startswith("/dev/"): line = line.split() disk, tcg_opal_dev = line[:2] # Check if it is SED device or not if "2" == tcg_opal_dev: sed_list.append(disk) self.do_debug(sed_list) def do_debug(self, sed_list): """ Collect debug logs """ for device in sed_list: self.add_cmd_output(f"sedutil-cli --query {device}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/skydive.py0000664000175000017500000000444314660147624016624 0ustar arifarif# Copyright (C) 2018 Masco Kaliyamoorthy # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Skydive(Plugin, RedHatPlugin): short_desc = 'Skydive network topology and protocol analyzer' plugin_name = "skydive" profiles = ('network', ) files = ( '/usr/bin/skydive', '/etc/skydive/skydive.yml' ) password_warn_text = " (password visible in process listings)" option_list = [ PluginOpt('username', default='', val_type=str, desc='skydive username'), PluginOpt('password', default='', val_type=str, desc='skydive password' + password_warn_text), PluginOpt('analyzer', default='', val_type=str, desc='skydive analyzer address') ] def setup(self): self.add_copy_spec("/etc/skydive/skydive.yml") self.add_copy_spec("/var/log/skydive.log") username = (self.get_option("username") or os.getenv("SKYDIVE_USERNAME", "") or os.getenv("OS_USERNAME", "")) password = (self.get_option("password") or os.getenv("SKYDIVE_PASSWORD", "") or os.getenv("OS_PASSWORD", "")) analyzer = (self.get_option("analyzer") or os.getenv("SKYDIVE_ANALYZER", "localhost:8082")) if not all([username, password, analyzer]): self.soslog.warning("Some or all of the skydive params are not " "set properly. Skydive status command may " " not work as expected.") # Setting all the params in environment variable for # skydive client access. os.environ["SKYDIVE_USERNAME"] = username os.environ["SKYDIVE_PASSWORD"] = password os.environ["SKYDIVE_ANALYZER"] = analyzer status_cmd = "skydive client status" self.add_cmd_output(status_cmd) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/lxd.py0000664000175000017500000000445214660147624015735 0ustar arifarif# Copyright (C) 2016 Jorge Niedbalski # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin, SoSPredicate class LXD(Plugin, UbuntuPlugin): short_desc = 'LXD container hypervisor' plugin_name = 'lxd' profiles = ('container',) packages = ('lxd',) commands = ('lxc', 'lxd',) services = ('snap.lxd.daemon', 'snap.lxd.activate') def setup(self): if self.is_snap: lxd_pred = SoSPredicate(self, services=['snap.lxd.daemon'], required={'services': 'all'}) self.add_cmd_output("lxd.buginfo", pred=lxd_pred, snap_cmd=True) self.add_copy_spec([ '/var/snap/lxd/common/config', '/var/snap/lxd/common/global-conf', '/var/snap/lxd/common/lxc/local.conf', '/var/snap/lxd/common/lxd/logs/*/*.conf', ]) if not self.get_option("all_logs"): self.add_copy_spec([ '/var/snap/lxd/common/lxd/logs/*.log', '/var/snap/lxd/common/lxd/logs/*/*.log', ]) else: self.add_copy_spec([ '/var/snap/lxd/common/lxd/logs/**', ]) else: lxd_pred = SoSPredicate(self, services=['lxd'], required={'services': 'all'}) self.add_copy_spec([ "/etc/default/lxd-bridge", "/var/log/lxd/*" ]) self.add_cmd_output([ "lxc image list", "lxc list", "lxc network list", "lxc profile list", "lxc storage list" ], pred=lxd_pred) self.add_cmd_output([ "find /var/lib/lxd -maxdepth 2 -type d -ls", ], suggest_filename='var-lxd-dirs.txt') def postproc(self): self.do_cmd_private_sub('lxd.buginfo') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/gssproxy.py0000664000175000017500000000146714660147624017047 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Robbie Harwood # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class GSSProxy(Plugin, IndependentPlugin): short_desc = 'GSSAPI Proxy' plugin_name = "gssproxy" profiles = ('services', 'security', 'identity') packages = ('gssproxy',) def setup(self): self.add_copy_spec([ "/etc/gssproxy/*.conf", "/etc/gss/mech.d/*" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/foreman_proxy.py0000664000175000017500000000445414660147624020040 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin) class ForemanProxy(Plugin): short_desc = 'Foreman Smart Proxy systems management' plugin_name = 'foreman_proxy' profiles = ('sysmgmt',) packages = ('foreman-proxy',) apachepkg = None def setup(self): self.add_file_tags({ '/var/log/foreman-proxy/proxy.log': 'foreman_proxy_log', '/etc/foreman-proxy/settings.yml': 'foreman_proxy_conf' }) self.add_forbidden_path([ "/etc/foreman-proxy/*key.pem" ]) self.add_copy_spec([ "/etc/foreman-proxy/", "/etc/smart_proxy_dynflow_core/settings.yml", "/var/log/foreman-proxy/*log*", f"/var/log/{self.apachepkg}*/katello-reverse-proxy_error_ssl.log*", f"/var/log/{self.apachepkg}*/rhsm-pulpcore-https-*access_ssl.log*", f"/var/log/{self.apachepkg}*/rhsm-pulpcore-https-*error_ssl.log*", f"/var/log/{self.apachepkg}*/katello-reverse-proxy_access_ssl.log*" ]) # collect http[|s]_proxy env.variables self.add_env_var(["http_proxy", "https_proxy"]) def postproc(self): self.do_path_regex_sub( r"/etc/foreman-proxy/(.*)((conf)(.*)?)", r"((\:|\s*)(passw|cred|token|secret|key).*(\:\s|=))(.*)", r"\1********") # yaml values should be alphanumeric self.do_path_regex_sub( r"/etc/foreman-proxy/(.*)((yaml|yml)(.*)?)", r"((\:|\s*)(passw|cred|token|secret|key).*(\:\s|=))(.*)", r'\1"********"') # Child classes needed to declare the apachepkg attr properly per distro class RedHatForemanProxy(ForemanProxy, RedHatPlugin): apachepkg = 'httpd' class DebianForemanProxy(ForemanProxy, DebianPlugin, UbuntuPlugin): apachepkg = 'apache2' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rear.py0000664000175000017500000000234214660147624016073 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Rear(Plugin, RedHatPlugin): short_desc = 'Relax and Recover' plugin_name = "rear" packages = ('rear',) def setup(self): # don't collect recovery ISOs or tar archives self.add_forbidden_path([ '/var/lib/rear/output/*' ]) self.add_copy_spec([ '/etc/rear/*conf', '/etc/rear/mappings/*', '/var/lib/rear/layout/*', '/var/lib/rear/recovery/*', '/var/log/rear/*log*' ]) self.add_cmd_output([ 'rear -V', 'rear dump' ]) def postproc(self): self.do_path_regex_sub( '/etc/rear/*', r'(SSH_ROOT_PASSWORD)=(.*)', r'\1=********' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/pacemaker.py0000664000175000017500000001347714660147624017105 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from datetime import datetime, timedelta from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) from sos.utilities import sos_parse_version class Pacemaker(Plugin): short_desc = 'Pacemaker high-availability cluster resource manager' plugin_name = "pacemaker" profiles = ("cluster", ) packages = ( "pacemaker", "pacemaker-remote", ) option_list = [ PluginOpt('crm-from', default='', val_type=str, desc='specfiy the start time for crm_report'), PluginOpt('crm-scrub', default=True, desc='enable crm_report password scrubbing') ] envfile = "" def setup_crm_mon(self): """ Get cluster summary """ self.add_cmd_output("crm_mon -1 -A -n -r -t") def setup_crm_shell(self): """ Get cluster status and configuration """ self.add_cmd_output([ "crm status", "crm configure show", ]) def setup_pcs(self): """ Get pacemaker/corosync configuration """ pcs_pkg = self.policy.package_manager.pkg_by_name('pcs') if pcs_pkg is None: return self.add_copy_spec("/var/log/pcsd/pcsd.log") self.add_cmd_output([ "pcs stonith sbd status --full", "pcs stonith sbd watchdog list", "pcs stonith history show", ]) pcs_version = '.'.join(pcs_pkg['version']) if sos_parse_version(pcs_version) > sos_parse_version('0.10.8'): self.add_cmd_output("pcs property config --all") else: self.add_cmd_output("pcs property list --all") self.add_cmd_output("pcs config", tags="pcs_config") self.add_cmd_output("pcs quorum status", tags="pcs_quorum_status") self.add_cmd_output("pcs status --full", tags="pcs_status") def postproc_crm_shell(self): """ Clear password """ self.do_cmd_output_sub( "crm configure show", r"passw([^\s=]*)=\S+", r"passw\1=********" ) def postproc_pcs(self): """ Clear password """ self.do_cmd_output_sub( "pcs config", r"passw([^\s=]*)=\S+", r"passw\1=********" ) def setup(self): self.add_copy_spec([ # Pacemaker 2.x default log locations "/var/log/pacemaker/pacemaker.log*", "/var/log/pacemaker/bundles/*/", "/var/log/pacemaker/pengine*", # Pacemaker 1.x default log locations "/var/log/pacemaker.log", "/var/log/pacemaker/bundles/*/", # Common user-specified locations "/var/log/cluster/pacemaker.log*", "/var/log/cluster/bundles/*/", ]) self.setup_crm_mon() # crm_report needs to be given a --from "YYYY-MM-DD HH:MM:SS" start # time in order to collect data. crm_from = (datetime.today() - timedelta(hours=72)).strftime("%Y-%m-%d %H:%m:%S") if self.get_option("crm-from"): if re.match(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', str(self.get_option("crm-from"))): crm_from = self.get_option("crm-from") else: self._log_error( f"crm_from parameter '{self.get_option('crm-from')}' is " "not a valid date: using default") crm_dest = self.get_cmd_output_path(name="crm_report", make=False) if self.get_option("crm-scrub"): crm_scrub = '-p "passw.*"' else: crm_scrub = "" self._log_warn("scrubbing of crm passwords has been disabled:") self._log_warn("data collected by crm_report may contain" " sensitive values.") self.add_cmd_output(f'crm_report --sos-mode {crm_scrub} -S -d ' f' --dest {crm_dest} --from "{crm_from}"', chroot=self.tmp_in_sysroot()) # collect user-defined logfiles, matching a shell-style syntax: # PCMK_logfile=filename # specified in the pacemaker start-up environment file. pattern = r'^\s*PCMK_logfile=[\'\"]?(\S+)[\'\"]?\s*(\s#.*)?$' if self.path_isfile(self.envfile): self.add_copy_spec(self.envfile) with open(self.envfile, 'r', encoding='UTF-8') as file: for line in file: if re.match(pattern, line): # remove trailing and leading quote marks, in case the # line is e.g. PCMK_logfile="/var/log/pacemaker.log" logfile = re.search(pattern, line).group(1) for regexp in [r'^"', r'"$', r'^\'', r'\'$']: logfile = re.sub(regexp, '', logfile) self.add_copy_spec(logfile) class DebianPacemaker(Pacemaker, DebianPlugin, UbuntuPlugin): def setup(self): self.envfile = self.path_join("/etc/default/pacemaker") self.setup_crm_shell() self.setup_pcs() super().setup() def postproc(self): self.postproc_crm_shell() self.postproc_pcs() class RedHatPacemaker(Pacemaker, RedHatPlugin): def setup(self): self.envfile = self.path_join("/etc/sysconfig/pacemaker") self.setup_pcs() self.add_copy_spec("/etc/sysconfig/sbd") super().setup() def postproc(self): self.postproc_pcs() # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/pulpcore.py0000664000175000017500000001620714660147624017000 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from re import match from shlex import quote from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class PulpCore(Plugin, IndependentPlugin): short_desc = 'Pulp-3 aka pulpcore' plugin_name = "pulpcore" commands = ("pulpcore-manager",) files = ("/etc/pulp/settings.py",) option_list = [ PluginOpt('task-days', default=7, desc='days of task history') ] dbhost = "localhost" dbport = 5432 dbname = "pulpcore" dbpasswd = "" staticroot = "/var/lib/pulp/assets" uploaddir = "/var/lib/pulp/media/upload" env = {"PGPASSWORD": dbpasswd} def parse_settings_config(self): """ Parse pulp settings """ databases_scope = False def separate_value(line, sep=':'): # an auxiliary method to parse values from lines like: # 'HOST': 'localhost', val = line.split(sep)[1].lstrip().rstrip(',') if (val.startswith('"') and val.endswith('"')) or \ (val.startswith('\'') and val.endswith('\'')): val = val[1:-1] return val try: with open("/etc/pulp/settings.py", 'r', encoding='UTF-8') as file: # split the lines to "one option per line" format for line in file.read() \ .replace(',', ',\n').replace('{', '{\n') \ .replace('}', '\n}').splitlines(): # skip empty lines and lines with comments if not line or line[0] == '#': continue if line.startswith("DATABASES"): databases_scope = True continue # example HOST line to parse: # 'HOST': 'localhost', pattern = r"\s*['|\"]%s['|\"]\s*:\s*\S+" if databases_scope and match(pattern % 'HOST', line): self.dbhost = separate_value(line) if databases_scope and match(pattern % 'PORT', line): self.dbport = separate_value(line) if databases_scope and match(pattern % 'NAME', line): self.dbname = separate_value(line) if databases_scope and match(pattern % 'PASSWORD', line): self.dbpasswd = separate_value(line) # if line contains closing '}' database_scope end if databases_scope and '}' in line: databases_scope = False if line.startswith("STATIC_ROOT = "): self.staticroot = separate_value(line, sep='=') if line.startswith("CHUNKED_UPLOAD_DIR = "): self.uploaddir = separate_value(line, sep='=') except IOError: # fallback when the cfg file is not accessible pass # set the password to os.environ when calling psql commands to prevent # printing it in sos logs # we can't set os.environ directly now: other plugins can overwrite it self.env = {"PGPASSWORD": self.dbpasswd} def setup(self): self.parse_settings_config() self.add_copy_spec([ "/etc/pulp/settings.py", "/etc/pki/pulp/*" ]) # skip collecting certificate keys self.add_forbidden_path("/etc/pki/pulp/**/*.key") self.add_cmd_output("curl -ks https://localhost/pulp/api/v3/status/", suggest_filename="pulp_status") dynaconf_env = {"LC_ALL": "en_US.UTF-8", "PULP_SETTINGS": "/etc/pulp/settings.py", "DJANGO_SETTINGS_MODULE": "pulpcore.app.settings"} self.add_cmd_output("dynaconf list", env=dynaconf_env) for _dir in [self.staticroot, self.uploaddir]: self.add_dir_listing(_dir) task_days = self.get_option('task-days') for table in ['core_task', 'core_taskgroup', 'core_groupprogressreport', 'core_progressreport']: _query = (f"select * from {table} where pulp_last_updated > NOW()" f" - interval '{task_days} days' order by" " pulp_last_updated") _cmd = self.build_query_cmd(_query) self.add_cmd_output(_cmd, env=self.env, suggest_filename=table) # collect tables sizes, ordered _cmd = self.build_query_cmd( "SELECT table_name, pg_size_pretty(total_bytes) AS total, " "pg_size_pretty(index_bytes) AS INDEX , " "pg_size_pretty(toast_bytes) AS toast, pg_size_pretty(table_bytes)" " AS TABLE FROM ( SELECT *, " "total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes " "FROM (SELECT c.oid,nspname AS table_schema, relname AS " "TABLE_NAME, c.reltuples AS row_estimate, " "pg_total_relation_size(c.oid) AS total_bytes, " "pg_indexes_size(c.oid) AS index_bytes, " "pg_total_relation_size(reltoastrelid) AS toast_bytes " "FROM pg_class c LEFT JOIN pg_namespace n ON " "n.oid = c.relnamespace WHERE relkind = 'r') a) a order by " "total_bytes DESC" ) self.add_cmd_output(_cmd, suggest_filename='pulpcore_db_tables_sizes', env=self.env) def build_query_cmd(self, query, csv=False): """ Builds the command needed to invoke the pgsql query as the postgres user. The query requires significant quoting work to satisfy both the shell and postgres parsing requirements. Note that this will generate a large amount of quoting in sos logs referencing the command being run """ if csv: query = f"COPY ({query}) TO STDOUT " \ "WITH (FORMAT 'csv', DELIMITER ',', HEADER)" _dbcmd = "psql --no-password -h %s -p %s -U pulp -d %s -c %s" return _dbcmd % (self.dbhost, self.dbport, self.dbname, quote(query)) def postproc(self): # obfuscate from /etc/pulp/settings.py and "dynaconf list": # SECRET_KEY = "eKfeDkTnvss7p5WFqYdGPWxXfHnsbDBx" # 'PASSWORD': 'tGrag2DmtLqKLTWTQ6U68f6MAhbqZVQj', # AUTH_LDAP_BIND_PASSWORD = 'ouch-a-secret' # the PASSWORD can be also in an one-liner list, so detect its value # in non-greedy manner till first ',' or '}' key_pass_re = r"((?:SECRET_KEY|AUTH_LDAP_BIND_PASSWORD)" \ r"(?:\<.+\>)?(\s*=)?|(password|PASSWORD)" \ r"(\"|'|:)+)\s*(\S*)" repl = r"\1 ********" self.do_path_regex_sub("/etc/pulp/settings.py", key_pass_re, repl) self.do_cmd_output_sub("dynaconf list", key_pass_re, repl) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/auditd.py0000664000175000017500000000352414660147624016417 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Auditd(Plugin, IndependentPlugin): short_desc = 'Audit daemon information' plugin_name = 'auditd' profiles = ('system', 'security') packages = ('audit',) def setup(self): self.add_copy_spec([ "/etc/audit/auditd.conf", "/etc/audit/audit.rules", "/etc/audit/audit-stop.rules", "/etc/audit/rules.d/", "/etc/audit/plugins.d/", "/etc/audisp/", ]) self.add_cmd_output( "ausearch -i --input-logs -m avc,user_avc,fanotify -ts today" ) self.add_cmd_output("auditctl -l", tags="auditctl_rules") self.add_cmd_output("auditctl -s", tags="auditctl_status") config_file = "/etc/audit/auditd.conf" log_file = "/var/log/audit/audit.log" try: with open(config_file, 'r', encoding='UTF-8') as cfile: for line in cfile.read().splitlines(): if not line: continue words = line.split('=') if words[0].strip() == 'log_file': log_file = words[1].strip() except IOError as error: self._log_error(f'Could not open conf file {config_file}: ' f'{error}') if not self.get_option("all_logs"): self.add_copy_spec(log_file) else: self.add_copy_spec(log_file+'*') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/omsa.py0000664000175000017500000000322314660147624016100 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OMSA(Plugin, IndependentPlugin): short_desc = 'Dell OpenManage Server Administrator (OMSA)' plugin_name = 'omsa' profiles = ('hardware', 'debug') files = ('/opt/dell/srvadmin/bin/omreport',) packages = ('srvadmin-omacore',) omreport = '/opt/dell/srvadmin/bin/omreport' def setup(self): self.add_copy_spec([ "/var/log/dell/updatepackage/log/support", "/opt/dell/srvadmin/var/log/openmanage/Inventory.xml*", "/opt/dell/srvadmin/etc/omreg.cfg", "/opt/dell/srvadmin/etc/openmanage/oma/ini", "/opt/dell/srvadmin/etc/srvadmin-deng/ini", "/opt/dell/srvadmin/etc/srvadmin-isvc/ini/d*ini", ]) self.add_cmd_output([ f"{self.omreport} system alertaction", f"{self.omreport} system alertlog", f"{self.omreport} system cmdlog", f"{self.omreport} system pedestinations", f"{self.omreport} system platformevents", f"{self.omreport} system summary", f"{self.omreport} system events", f"{self.omreport} chassis info", f"{self.omreport} chassis biossetup", f"{self.omreport} storage controller", ], timeout=30) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/sssd.py0000664000175000017500000000452414660147624016122 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Pierre Carrier # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, SoSPredicate) class Sssd(Plugin): short_desc = 'System security service daemon' plugin_name = "sssd" profiles = ('services', 'security', 'identity') packages = ('sssd', 'sssd-common') def setup(self): self.add_copy_spec([ # main config file "/etc/sssd/sssd.conf", # SSSD 1.14 "/etc/sssd/conf.d/*.conf", # dynamic Kerberos configuration "/var/lib/sss/pubconf/krb5.include.d/*" ]) # add individual log files self.add_copy_spec(glob("/var/log/sssd/*log*"), tags='sssd_logs') # add memory cache self.add_copy_spec([ "/var/lib/sss/mc/passwd", "/var/lib/sss/mc/group", "/var/lib/sss/mc/initgroups" ]) # call sssctl commands only when sssd service is running, # otherwise the command timeouts sssd_pred = SoSPredicate(self, services=["sssd"]) self.add_cmd_output("sssctl config-check", pred=sssd_pred) # if predicate fails, domain["status"] = None and thus we skip parsing # missing output domain = self.collect_cmd_output("sssctl domain-list", pred=sssd_pred) if domain['status'] == 0: for domain_name in domain['output'].splitlines(): self.add_cmd_output("sssctl domain-status -o " + domain_name) def postproc(self): regexp = r"((\s*ldap_default_authtok\s*=)(.*))" self.do_file_sub("/etc/sssd/sssd.conf", regexp, r"\2 ********") self.do_path_regex_sub("/etc/sssd/conf.d/*", regexp, r"\2 ********") class RedHatSssd(Sssd, RedHatPlugin): pass class DebianSssd(Sssd, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec("/etc/default/sssd") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/foreman_installer.py0000664000175000017500000000746514660147624020661 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin) class ForemanInstaller(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'Foreman installer and maintainer' plugin_name = 'foreman_installer' profiles = ('sysmgmt',) packages = ('foreman-installer', 'rubygem-foreman_maintain') def setup(self): self.add_copy_spec([ "/etc/foreman-installer/*", "/var/log/foreman-installer/*", "/var/log/foreman-maintain/*", "/var/lib/foreman-maintain/data.yml", "/etc/foreman-maintain/foreman_maintain.yml", # specifically collect .applied files # that would be skipped otherwise as hidden files "/etc/foreman-installer/scenarios.d/*/.applied", ]) # skip collecting individual migration scripts; # .applied file in each dir is still self.add_forbidden_path( "/etc/foreman-installer/scenarios.d/*.migrations/*.rb" ) self.add_cmd_output([ 'foreman-maintain service status', ]) def postproc(self): install_logs = "/var/log/foreman-installer/" logsreg = r"((foreman.*)?(\"::(foreman(.*?)|katello).*)?((::(.*)::.*" \ r"(passw|cred|token|secret|key).*(\")?:)|(storepass )" \ r"|(password =)))(.*)" self.do_path_regex_sub(install_logs, logsreg, r"\1 ********") # need to do two passes here, debug output has different formatting logs_debug_reg = (r"(\s)+(Found key: (\"(foreman(.*?)|katello)" r"::(.*(token|secret|key|passw).*)\") value:) " r"(.*)") self.do_path_regex_sub(install_logs, logs_debug_reg, r"\1 \2 ********") # also hide passwords in yet different formats self.do_path_regex_sub( install_logs, r"((\.|_|-)password(=\'|=|\", \"))(\w*)", r"\1********") self.do_path_regex_sub( "/var/log/foreman-installer/foreman-proxy*", r"(\s*proxy_password\s=) (.*)", r"\1 ********") self.do_path_regex_sub( "/var/log/foreman-maintain/foreman-maintain.log*", r"(((passw|cred|token|secret)=)|(password ))(.*)", r"\1********") # all scrubbing applied to configs must be applied to installer logs # as well, since logs contain diff of configs self.do_path_regex_sub( r"(/etc/foreman-(installer|maintain)/(.*)((conf)(.*)?))|" fr"({install_logs})", r"((\:|\s*)(passw|cred|token|secret|key).*(\:\s|=))(.*)", r"\1********") # yaml values should be alphanumeric self.do_path_regex_sub( r"(/etc/foreman-(installer|maintain)/(.*)((yaml|yml)(.*)?))|" fr"({install_logs})", r"((\:|\s*)(passw|cred|token|secret|key).*(\:\s|=))(.*)", r'\1"********"') # Add Red Hat Insights tags for RedHatPlugin only class RedHatForemanInstaller(ForemanInstaller, RedHatPlugin): def setup(self): self.add_file_tags({ '/var/log/foreman-installer/satellite.log': ['foreman_satellite_log', 'satellite_installer_log'], '/var/log/foreman-installer/capsule.log': ['capsule_log', 'capsule_installer_log'], }) super().setup() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nodejs.py0000664000175000017500000000227114660147624016425 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Tomas Tomecek # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, SuSEPlugin) class NodeJS(Plugin, RedHatPlugin, SuSEPlugin): short_desc = 'NodeJS runtime version' plugin_name = 'nodejs' profiles = ('system',) packages = ('nodejs',) def setup(self): # we could get much more info with: # p = require("process"); console.log(p) # unfortunately 'process' module is not available on 0.10 self.add_cmd_output("node -v", suggest_filename="nodejs-version") class NodeJSUbuntu(NodeJS, UbuntuPlugin, DebianPlugin): """ Ubuntu/Debian require nodejs-legacy package in order to have a node executable """ packages = ('nodejs', 'nodejs-legacy') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/libvirt.py0000664000175000017500000000741614660147624016624 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob from sos.report.plugins import Plugin, IndependentPlugin class Libvirt(Plugin, IndependentPlugin): short_desc = 'libvirt virtualization API' plugin_name = 'libvirt' profiles = ('system', 'virt', 'openstack_edpm') def setup(self): libvirt_keytab = "/etc/libvirt/krb5.tab" # authentication databases used for libvirt SASL authentication self.add_forbidden_path([ "/etc/libvirt/passwd.db", "/etc/libvirt/krb5.tab", "/var/lib/libvirt/qemu/*/master-key.aes", "/etc/libvirt/secrets" ]) self.add_copy_spec([ "/etc/libvirt/libvirt.conf", "/etc/libvirt/libvirtd.conf", "/etc/libvirt/lxc.conf", "/etc/libvirt/nwfilter/*.xml", "/etc/libvirt/qemu/*.xml", "/etc/libvirt/qemu.conf", "/run/libvirt/", "/etc/libvirt/qemu/networks/*.xml", "/etc/libvirt/qemu/networks/autostart/*.xml", "/etc/libvirt/storage/*.xml", "/etc/libvirt/storage/autostart/*.xml", "/etc/libvirt/qemu-lockd.conf", "/etc/libvirt/virtlockd.conf", "/etc/libvirt/virtlogd.conf", "/var/lib/libvirt/dnsmasq/*", "/var/lib/libvirt/qemu/snapshot/*/*.xml", "/var/lib/openstack/config/libvirt", "/var/lib/openstack/containers/libvirt*.json", ]) if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/libvirt/libvirtd.log", "/var/log/libvirt/qemu/*.log*", "/var/log/libvirt/lxc/*.log", "/var/log/libvirt/uml/*.log", "/var/log/swtpm/libvirt/qemu/*.log", "/var/log/containers/libvirt/libvirtd.log", "/var/log/containers/libvirt/qemu/*.log*", "/var/log/containers/libvirt/lxc/*.log", "/var/log/containers/libvirt/swtpm/libvirt/qemu/*.log", "/var/log/containers/libvirt/uml/*.log", "/var/log/containers/qemu/*.log", "/var/log/containers/libvirt/*.log", ]) else: self.add_copy_spec([ "/var/log/libvirt", "/var/log/containers/qemu/", "/var/log/containers/libvirt/", ]) if self.path_exists(self.path_join(libvirt_keytab)): self.add_cmd_output(f"klist -ket {libvirt_keytab}") self.add_dir_listing("/var/lib/libvirt/qemu", recursive=True) # get details of processes of KVM hosts for pidfile in glob.glob("/run/libvirt/*/*.pid"): with open(pidfile, 'r', encoding='UTF-8') as pfile: pid = pfile.read().splitlines()[0] pr_files = ["environ", "cgroup", "maps", "numa_maps", "limits"] for file in pr_files: self.add_copy_spec(f"/proc/{pid}/{file}") self.add_file_tags({ "/run/libvirt/qemu/*.xml": "var_qemu_xml", "/var/log/libvirt/qemu/*.log": "libvirtd_qemu_log" }) def postproc(self): match_exp = r"(\s*passwd=\s*')([^']*)('.*)" libvirt_path_exps = [ r"/etc/libvirt/qemu/.*\.xml", r"/run/libvirt/qemu/.*\.xml", r"/etc/libvirt/.*\.conf" ] for path_exp in libvirt_path_exps: self.do_path_regex_sub(path_exp, match_exp, r"\1******\3") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/proxmox.py0000664000175000017500000000575014660147624016664 0ustar arifarif# Copyright (C) 2024 ORNESS/Ditrit Drien Breton # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from sos.report.plugins import Plugin, DebianPlugin, PluginOpt class Proxmox(Plugin, DebianPlugin): """ This plugin will capture information about the system's Proxmox Virtualization Environment. It will collect information about the cluster, nodes, pools and storage from the Proxmox API. """ short_desc = 'Proxmox cluster information' plugin_name = 'proxmox' packages = ('proxmox-ve',) option_list = [ PluginOpt('output-formats', desc='List of output formats to use ' 'for the commands separated by ":".', default='text', ), ] def setup(self): output_formats = self.get_option('output-formats').split(':') commands = [ 'cluster/resources', 'cluster/config/nodes', 'cluster/options', 'nodes/:id/status', 'nodes/:id/storage', 'nodes/:id/network', 'pools', 'storage', 'storage/:sid', 'cluster/ceph/status', 'cluster/ceph/metadata', 'cluster/ceph/flags', ] cmd_paths = [] for command in commands: cmd_paths.extend(self.build_cmd_paths(command)) self.add_cmd_output([ f"pvesh get {cmd} {'--noborder' if format == 'text' else ''} " f"--output-format {format}" for cmd in cmd_paths for format in output_formats ]) def build_cmd_paths(self, base_path): """ Build command paths, replacing dynamic attributes with data from the API :param base_path: The base path to build from :type base_path: str :return: A list of paths """ parts = base_path.split('/') paths = [] id_index = next((i for i, part in enumerate(parts) if part.startswith(':')), None) if id_index is not None: path = "/".join(parts[:id_index]) trailing_path = "/".join(parts[id_index + 1:]) results = self.exec_cmd(f'pvesh ls {path} --output-format json') if results["status"] == 0: children = json.loads(results["output"]) for child in children: paths.extend( self.build_cmd_paths( f"{path}/{child['name']}/{trailing_path}" ) ) else: paths.append('/'.join(parts)) return paths # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/docker_distribution.py0000664000175000017500000000246214660147624021213 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class DockerDistribution(Plugin): short_desc = 'Docker Distribution' plugin_name = "docker_distribution" profiles = ('container',) def setup(self): self.add_copy_spec('/etc/docker-distribution/') self.add_journal('docker-distribution') conf = self.path_join('/etc/docker-distribution/registry/config.yml') if self.path_exists(conf): with open(conf, encoding='UTF-8') as file: for line in file: if 'rootdirectory' in line: loc = line.split()[1] self.add_dir_listing(loc, tree=True) class RedHatDockerDistribution(DockerDistribution, RedHatPlugin): packages = ('docker-distribution',) def setup(self): self.add_forbidden_path('/etc/docker-distribution/registry/*passwd') super().setup() sos-4.8.0/sos/report/plugins/nvidia.py0000664000175000017500000000317514660147624016421 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Nvidia(Plugin, IndependentPlugin): short_desc = 'Nvidia GPU information' plugin_name = 'nvidia' commands = ('nvidia-smi',) def setup(self): subcmds = [ '--list-gpus', '-q -d PERFORMANCE', '-q -d SUPPORTED_CLOCKS', '-q -d PAGE_RETIREMENT', '-q', '-q -d ECC', 'nvlink -s', 'nvlink -e' ] self.add_service_status("nvidia-persistenced") self.add_cmd_output([f"nvidia-smi {cmd}" for cmd in subcmds]) query = ('gpu_name,gpu_bus_id,vbios_version,temperature.gpu,' 'utilization.gpu,memory.total,memory.free,memory.used,' 'clocks.applications.graphics,clocks.applications.memory') querypages = ('timestamp,gpu_bus_id,gpu_serial,gpu_uuid,' 'retired_pages.address,retired_pages.cause') self.add_cmd_output(f"nvidia-smi --query-gpu={query} --format=csv") self.add_cmd_output( f"nvidia-smi --query-retired-pages={querypages} --format=csv" ) self.add_journal(boot=0, identifier='nvidia-persistenced') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/xen.py0000664000175000017500000000651014660147624015735 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import re from sos.report.plugins import Plugin, RedHatPlugin class Xen(Plugin, RedHatPlugin): short_desc = 'Xen virtualization' plugin_name = 'xen' profiles = ('virt',) def determine_xen_host(self): """ Determine xen host type """ if os.access("/proc/acpi/dsdt", os.R_OK): result = self.exec_cmd("grep -qi xen /proc/acpi/dsdt") if result['status'] == 0: return "hvm" if os.access("/proc/xen/capabilities", os.R_OK): result = self.exec_cmd("grep -q control_d /proc/xen/capabilities") if result['status'] == 0: return "dom0" return "domU" return "baremetal" def check_enabled(self): return self.determine_xen_host() == "baremetal" def is_running_xenstored(self): """ Check if xenstored is running """ xs_pid = self.exec_cmd("pidof xenstored")['output'] xs_pidnum = re.split('\n$', xs_pid)[0] return xs_pidnum.isdigit() def dom_collect_proc(self): """ Collect /proc/xen """ self.add_copy_spec([ "/proc/xen/balloon", "/proc/xen/capabilities", "/proc/xen/xsd_kva", "/proc/xen/xsd_port"]) # determine if CPU has PAE support self.add_cmd_output("grep pae /proc/cpuinfo") # determine if CPU has Intel-VT or AMD-V support self.add_cmd_output("egrep -e 'vmx|svm' /proc/cpuinfo") def setup(self): host_type = self.determine_xen_host() if host_type == "domU": # we should collect /proc/xen and /sys/hypervisor self.dom_collect_proc() # determine if hardware virtualization support is enabled # in BIOS: /sys/hypervisor/properties/capabilities self.add_copy_spec("/sys/hypervisor") elif host_type == "hvm": # what do we collect here??? pass elif host_type == "dom0": # default of dom0, collect lots of system information self.add_copy_spec([ "/var/log/xen", "/etc/xen", "/sys/hypervisor/version", "/sys/hypervisor/compilation", "/sys/hypervisor/properties", "/sys/hypervisor/type"]) self.add_cmd_output([ "xm dmesg", "xm info", "xm list", "xm list --long", "bridge link show" ]) self.dom_collect_proc() if self.is_running_xenstored(): self.add_copy_spec("/sys/hypervisor/uuid") self.add_cmd_output("xenstore-ls") else: # we need tdb instead of xenstore-ls if cannot get it. self.add_copy_spec("/var/lib/xenstored/tdb") else: # for bare-metal, we don't have to do anything special return # USEFUL self.add_custom_text("Xen hostType: "+host_type) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/azure.py0000664000175000017500000000457014660147624016275 0ustar arifarif# Copyright (C) 2013 Adam Stokes # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, UbuntuPlugin, RedHatPlugin class Azure(Plugin, UbuntuPlugin): short_desc = 'Microsoft Azure client' plugin_name = 'azure' profiles = ('virt',) packages = ('WALinuxAgent',) def setup(self): self.add_copy_spec([ "/var/log/waagent*", "/var/lib/cloud", "/etc/default/kv-kvp-daemon-init", "/etc/waagent.conf", "/sys/module/hv_netvsc/parameters/ring_size", "/sys/module/hv_storvsc/parameters/storvsc_ringbuffer_size", "/var/lib/AzureEnhancedMonitor" ]) # Adds all files under /var/log/azure to the sos report # os.walk is used because /var/log/azure is used by multiple Azure # extensions and there is no standard log filename format limit = self.get_option("log_size") for path, _, files in os.walk("/var/log/azure"): for name in files: self.add_copy_spec(self.path_join(path, name), sizelimit=limit) self.add_cmd_output(( 'curl -s -H Metadata:true --noproxy "*" ' '"http://169.254.169.254/metadata/instance/compute?' 'api-version=2021-01-01&format=json"' ), suggest_filename='instance_metadata.json') class RedHatAzure(Azure, RedHatPlugin): def setup(self): super().setup() if self.path_isfile('/etc/yum.repos.d/rh-cloud.repo'): curl_cmd = ('curl -s -m 5 -vvv ' 'https://rhui-%s.microsoft.com/pulp/repos/%s') self.add_cmd_output([ curl_cmd % ('1', 'microsoft-azure-rhel7'), curl_cmd % ('2', 'microsoft-azure-rhel7'), curl_cmd % ('3', 'microsoft-azure-rhel7') ]) crt_path = '/etc/pki/rhui/product/content.crt' if self.path_isfile(crt_path): self.add_cmd_output([ 'openssl x509 -noout -text -in ' + crt_path ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/aap_gateway.py0000664000175000017500000000332214660147624017423 0ustar arifarif# Copyright (c) 2024 Lucas Benedito # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class AAPGatewayPlugin(Plugin, RedHatPlugin): short_desc = 'AAP Gateway plugin' plugin_name = 'aap_gateway' profiles = ('sysmgmt', 'ansible',) packages = ('automation-gateway', 'automation-gateway-config') commands = ('aap-gateway-manage',) services = ('automation-gateway',) def setup(self): self.add_copy_spec([ "/var/log/supervisor", "/etc/ansible-automation-platform", "/etc/supervisord.d/", "/var/log/ansible-automation-platform/gateway/", ]) self.add_forbidden_path([ "/etc/ansible-automation-platform/gateway/SECRET_KEY", "/etc/ansible-automation-platform/gateway/*.key", "/etc/ansible-automation-platform/gateway/*.cert", ]) self.add_cmd_output("aap-gateway-manage list_services") self.add_dir_listing("/etc/ansible-automation-platform/", recursive=True) def postproc(self): # remove database password jreg = r"(DATABASE_PASSWORD)(\s*)(=|:)(\s*)(.*)" repl = r"\1\2\3\4********" self.do_path_regex_sub( "/etc/ansible-automation-platform/gateway/settings.py", jreg, repl) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/containerd.py0000664000175000017500000000164714660147624017277 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, UbuntuPlugin, CosPlugin) class Containerd(Plugin, RedHatPlugin, UbuntuPlugin, CosPlugin): short_desc = 'Containerd containers' plugin_name = 'containerd' profiles = ('container',) packages = ('containerd', 'containerd.io',) def setup(self): self.add_copy_spec([ "/etc/containerd/", "/etc/cni/net.d/", ]) self.add_cmd_output('containerd config dump') # collect the containerd logs. self.add_journal(units='containerd') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/named.py0000664000175000017500000000477714660147624016244 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from os.path import join, normpath from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Named(Plugin): short_desc = 'BIND named server' plugin_name = "named" profiles = ('system', 'services', 'network') named_conf = "/etc/named.conf" config_files = named_conf def setup(self): self.add_copy_spec([ "/etc/default/bind", "/var/log/named*.log" ]) for cfg in self.config_files: if self.path_exists(cfg): self.add_copy_spec([ cfg, self.get_dns_dir(cfg) ]) self.add_forbidden_path([ join(self.get_dns_dir(cfg), "chroot/dev"), join(self.get_dns_dir(cfg), "chroot/proc") ]) def get_dns_dir(self, config_file): """ grab directory path from named{conf,boot} """ directory_list = self.do_regex_find_all(r"directory\s+\"(.*)\"", config_file) if directory_list: return normpath(directory_list[0]) return "" def postproc(self): match = r"(\s*arg \"password )[^\"]*" subst = r"\1******" self.do_file_sub(self.named_conf, match, subst) class RedHatNamed(Named, RedHatPlugin): named_conf = "/etc/named.conf" config_files = ("/etc/named.conf", "/etc/named.boot") files = (named_conf, '/etc/sysconfig/named') packages = ('bind',) def setup(self): super().setup() self.add_copy_spec("/etc/named/") self.add_copy_spec("/etc/sysconfig/named") self.add_cmd_output("klist -ket /etc/named.keytab") self.add_forbidden_path("/etc/named.keytab") class DebianNamed(Named, DebianPlugin, UbuntuPlugin): files = ('/etc/bind/named.conf',) packages = ('bind9',) named_conf = "/etc/bind/named.conf" config_files = (named_conf, "/etc/bind/named.conf.options", "/etc/bind/named.conf.local") def setup(self): super().setup() self.add_copy_spec("/etc/bind/") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sudo.py0000664000175000017500000000155114660147624016115 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Sudo(Plugin, IndependentPlugin): short_desc = 'Sudo command execution' plugin_name = 'sudo' profiles = ('identity', 'system') packages = ('sudo',) def setup(self): self.add_copy_spec("/etc/sudo*") def postproc(self): regexp = r"(\s*bindpw\s*)\S+" self.do_file_sub("/etc/sudo-ldap.conf", regexp, r"\1********") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/quagga.py0000664000175000017500000000136314660147624016411 0ustar arifarif# Copyright (C) 2007 Ranjith Rajaram # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Quagga(Plugin, RedHatPlugin): short_desc = 'Quagga routing service' plugin_name = 'quagga' profiles = ('network',) files = ('/etc/quagga/zebra.conf',) packages = ('quagga',) def setup(self): self.add_copy_spec("/etc/quagga/") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/crypto.py0000664000175000017500000000260414660147624016463 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Crypto(Plugin, IndependentPlugin): short_desc = 'System crypto services information' plugin_name = 'crypto' profiles = ('system', 'hardware') def setup(self): cpth = '/etc/crypto-policies/back-ends' self.add_file_tags({ f"{cpth}/bind.config": 'crypto_policies_bind', f"{cpth}/opensshserver.config": 'crypto_policies_opensshserver', '/etc/crypto-policies/.*/current': 'crypto_policies_state_current', '/etc/crypto-policies/config': 'crypto_policies_config' }) self.add_copy_spec([ "/proc/crypto", "/proc/sys/crypto/fips_enabled", "/etc/system-fips", "/etc/crypto-policies/*" ]) self.add_cmd_output([ "fips-mode-setup --check", "update-crypto-policies --show", "update-crypto-policies --is-applied" ]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/saphana.py0000664000175000017500000000502414660147624016555 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Saphana(Plugin, RedHatPlugin): short_desc = 'SAP HANA' plugin_name = 'saphana' profiles = ('sap',) files = ('/hana',) def setup(self): sids = [] if self.path_isdir("/hana/shared"): shared = self.listdir("/hana/shared") for sid in shared: if len(sid) == 3: sid = sid.strip() sids.append(sid) for sid in sids: sidadm = f'{sid.lower()}adm' prefix = f'su - {sidadm} -c' self.add_cmd_output(f'{prefix} "HDB info"', suggest_filename=f"{sid}_HDB_info") self.add_cmd_output(f'{prefix} "hdbsrvutil -v"', suggest_filename=f"{sid}_version") self.add_cmd_output(f'{prefix} \'hdbcons "mm l -s -S -p"\'', suggest_filename=f"{sid}_memusage") self.add_cmd_output(f'{prefix} \'hdbcons -e hdbindexserver \ "replication info"\'', suggest_filename=f"{sid}_replicainfo") if self.path_isdir(f"/hana/shared/{sid}/"): for inst in self.listdir(f"/hana/shared/{sid}/"): if "HDB" in inst: inst = inst.strip()[-2:] self.get_inst_info(sid, sidadm, inst) def get_inst_info(self, sid, sidadm, inst): """ Collect the given instance info """ proc_cmd = 'su - %s -c "sapcontrol -nr %s -function GetProcessList"' status_fname = f"{sid}_{inst}_status" self.add_cmd_output( proc_cmd % (sidadm, inst), suggest_filename=status_fname ) path = f"/usr/sap/{sid}/HDB{inst}/exe/python_support" if self.path_isdir(path): py_cmd = 'su - %s -c "python %s/landscapeHostConfiguration.py"' py_fname = f"{sid}_{inst}_landscapeConfig" self.add_cmd_output( py_cmd % (sidadm, path), suggest_filename=py_fname ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/java.py0000664000175000017500000000172214660147624016064 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Java(Plugin, IndependentPlugin): short_desc = 'Java runtime' plugin_name = "java" profiles = ('webserver', 'java') verify_packages = ('java.*',) commands = ('java',) files = ('/usr/bin/java',) packages = ('java', 'java-common', ) def setup(self): self.add_copy_spec("/etc/java*") self.add_forbidden_path("/etc/java*/security") self.add_cmd_output("alternatives --display java") self.add_cmd_output("readlink -f /usr/bin/java") self.add_cmd_output("java -version") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cloud_init.py0000664000175000017500000000211214660147624017266 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class CloudInit(Plugin, IndependentPlugin): short_desc = 'cloud-init instance configurations' plugin_name = 'cloud_init' packages = ('cloud-init',) services = ('cloud-init',) def setup(self): self.add_cmd_output([ 'cloud-init --version', 'cloud-init features', 'cloud-init status' ]) self.add_copy_spec([ '/etc/cloud/', '/run/cloud-init/', '/var/log/cloud-init*' ]) self.add_file_tags({ "/etc/cloud/cloud.cfg": "cloud_cfg_filtered" }) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/vectordev.py0000664000175000017500000000231514660147624017143 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class VectorDev(Plugin, IndependentPlugin): short_desc = 'A tool for building observability pipelines' plugin_name = "vectordev" profiles = ('observability',) files = ('/etc/vector/',) def setup(self): self.add_copy_spec([ "/etc/vector/" ]) def postproc(self): vector_config_path = "/etc/vector/*" protect_keys = [ "auth.password", "auth.token", "tls.key_pass", ] # Redact yaml and ini style "key (:|=) value". keys_regex = fr"(^\s*({'|'.join(protect_keys)})\s*(:|=)\s*)(.*)" sub_regex = r"\1*********" self.do_path_regex_sub(vector_config_path, keys_regex, sub_regex) # Redact certificates self.do_file_private_sub(vector_config_path) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/flatpak.py0000664000175000017500000000245614660147624016572 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Flatpak(Plugin, IndependentPlugin): short_desc = 'Flatpak' plugin_name = 'flatpak' profiles = ('sysmgmt', 'packagemanager') commands = ("flatpak",) packages = ("flatpak",) def setup(self): env = {"GVFS_REMOTE_VOLUME_MONITOR_IGNORE": "1"} self.add_cmd_output([ "flatpak --version", "flatpak --default-arch", "flatpak --supported-arches", "flatpak --gl-drivers", "flatpak --installations", "flatpak --print-updated-env", "flatpak config", "flatpak remote-list --show-details", "flatpak list --runtime --show-details", "flatpak list --app --show-details", "flatpak history --columns=all", ], env=env) if self.get_option("verify"): self.add_cmd_output("flatpak repair --dry-run", env=env) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/anacron.py0000664000175000017500000000141614660147624016564 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Anacron(Plugin, IndependentPlugin): short_desc = 'Anacron job scheduling service' plugin_name = 'anacron' profiles = ('system',) packages = ('anacron', 'chronie-anacron') # anacron may be provided by anacron, cronie-anacron etc. # just look for the configuration file which is common files = ('/etc/anacrontab',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_mon.py0000664000175000017500000002265014660147624016736 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephMON(Plugin, RedHatPlugin, UbuntuPlugin): """ This plugin serves to collect information on monitor nodes within a Ceph or microceph cluster. It is designed to collect from several versions of Ceph, including versions that serve as the basis for RHCS 4 and RHCS 5. Older versions of Ceph will have collections from locations such as /var/log/ceph, whereas newer versions (as of this plugin's latest update) will have collections from /var/log/ceph//. This plugin attempts to account for this where possible across the host's filesystem. Users may expect to see several collections twice - once in standard output from the `ceph` command, and again in JSON format. The latter of which will be placed in the `json_output/` subdirectory within this plugin's directory in the report archive. These JSON formatted collections are intended to aid in automated analysis. """ short_desc = 'CEPH mon' plugin_name = 'ceph_mon' profiles = ('storage', 'virt', 'container', 'ceph') # note: for RHCS 5 / Ceph v16 the containers serve as an enablement trigger # but by default they are not capable of running various ceph commands in # this plugin - the `ceph` binary is functional directly on the host containers = ('ceph-(.*-)?mon.*',) files = ('/var/lib/ceph/mon/*', '/var/lib/ceph/*/mon*', '/var/snap/microceph/common/data/mon/*') ceph_version = 0 def setup(self): all_logs = self.get_option("all_logs") self.ceph_version = self.get_ceph_version() microceph_pkg = self.policy.package_manager.pkg_by_name('microceph') if not microceph_pkg: self.add_file_tags({ '.*/ceph.conf': 'ceph_conf', "/var/log/ceph/(.*/)?ceph-.*mon.*.log": 'ceph_mon_log' }) self.add_forbidden_path([ "/etc/ceph/*keyring*", "/var/lib/ceph/**/*keyring*", # Excludes temporary ceph-osd mount location like # /var/lib/ceph/tmp/mnt.XXXX from sos collection. "/var/lib/ceph/**/tmp/*mnt*", "/etc/ceph/*bindpass*" ]) if not all_logs: self.add_copy_spec([ "/var/log/ceph/**/*ceph-mon*.log" ]) else: self.add_copy_spec([ "/var/log/ceph/**/*ceph-mon*.log*" ]) self.add_copy_spec([ "/run/ceph/**/ceph-mon*", "/var/lib/ceph/**/kv_backend", ]) else: self.add_forbidden_path([ "/var/snap/microceph/common/**/*keyring*", "/var/snap/microceph/current/**/*keyring*", "/var/snap/microceph/common/data/mon/*/store.db", "/var/snap/microceph/common/state/*", ]) if not all_logs: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-mon*.log", ]) else: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-mon*.log*", ]) self.add_copy_spec([ "/var/snap/microceph/common/data/mon/*", "/var/snap/microceph/current/conf/*", ]) self.add_cmd_output("ceph report", tags="ceph_report") self.add_cmd_output([ # The ceph_mon plugin will collect all the "ceph ..." commands # which typically require the keyring. "ceph config dump", "ceph config generate-minimal-conf", "ceph config log", "ceph config-key dump", "ceph crash stat", "ceph features", "ceph health detail", "ceph insights", "ceph log last 10000 debug audit", "ceph log last 10000 debug cluster", "ceph mgr dump", "ceph mgr metadata", "ceph mgr module ls", "ceph mgr services", "ceph mgr versions", "ceph mon stat", "ceph osd crush dump", "ceph osd crush show-tunables", "ceph osd crush tree --show-shadow", "ceph osd erasure-code-profile ls", "ceph osd metadata", "ceph quorum_status", "ceph versions", "ceph-disk list", ]) crashes = self.collect_cmd_output('ceph crash ls') if crashes['status'] == 0: for crashln in crashes['output'].splitlines(): if crashln.endswith('*'): cid = crashln.split()[0] self.add_cmd_output(f"ceph crash info {cid}") ceph_cmds = [ "device ls", "df detail", "df", "fs dump", "fs ls", "mds stat", "mon dump", "osd blocked-by", "osd df tree", "osd df", "osd dump", "osd numa-status", "osd perf", "osd pool autoscale-status", "osd pool ls detail", "osd stat", "pg dump", "pg stat", "status", "time-sync-status", ] self.add_cmd_output("ceph health detail --format json-pretty", subdir="json_output", tags="ceph_health_detail") self.add_cmd_output("ceph osd tree --format json-pretty", subdir="json_output", tags="ceph_osd_tree") self.add_cmd_output( [f"ceph tell mon.{mid} mon_status" for mid in self.get_ceph_ids()], subdir="json_output", ) self.add_cmd_output([f"ceph {cmd}" for cmd in ceph_cmds]) # get ceph_cmds again as json for easier automation parsing self.add_cmd_output( [f"ceph {cmd} --format json-pretty" for cmd in ceph_cmds], subdir="json_output", ) def get_ceph_version(self): """ Get the versions of running daemons """ ver = self.exec_cmd('ceph --version') if ver['status'] == 0: try: _ver = ver['output'].split()[2] return int(_ver.split('.')[0]) except Exception as err: # pylint: disable=broad-except self._log_debug(f"Could not determine ceph version: {err}") self._log_error( 'Failed to find ceph version, command collection will be limited' ) return 0 def get_ceph_ids(self): """ Get the IDs of the Ceph daemons """ ceph_ids = [] # ceph version 14 correlates to RHCS 4 if self.ceph_version in (14, 15): # Get the ceph user processes out = self.exec_cmd('ps -u ceph -o args') if out['status'] == 0: # Extract the mon ids for procs in out['output'].splitlines(): proc = procs.split() # Locate the '--id' value of the process if proc and proc[0].endswith("ceph-mon"): try: id_index = proc.index("--id") ceph_ids.append(proc[id_index + 1]) except (IndexError, ValueError): self._log_warn('Unable to find ceph IDs') # ceph version 16 is RHCS 5 elif self.ceph_version >= 16: stats = self.exec_cmd('ceph status') if stats['status'] == 0: try: ret = re.search(r'(\s*mon: .* quorum) (.*) (\(.*\))', stats['output']) ceph_ids.extend(ret.groups()[1].split(',')) except Exception as err: # pylint: disable=broad-except self._log_debug(f"id determination failed: {err}") return ceph_ids def postproc(self): if self.ceph_version >= 16: keys = [ 'key', 'username', 'password', '_secret', 'rbd/mirror/peer/.*' ] # we need to do this iteratively, as config-key dump here contains # nested json data written as strings, which may have multiple hits # within the same line for key in keys: creg = fr'(((.*)({key}\\\": ))((\\\"(.*?)\\\")(.*)))' self.do_cmd_output_sub( 'ceph config-key dump', creg, r'\2\"******\"\8' ) else: keys = [ 'API_PASSWORD', 'API_USER.*', 'API_.*_KEY', 'key', '_secret', 'rbd/mirror/peer/.*' ] creg = fr"((\".*({'|'.join(keys)})\":) \")(.*)(\".*)" self.do_cmd_output_sub( 'ceph config-key dump', creg, r'\1*******\5' ) self.do_cmd_private_sub('ceph config-key dump') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dnf.py0000664000175000017500000001243314660147624015713 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Sachin Patil # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class DNFPlugin(Plugin, RedHatPlugin): """ The DNF plugin collects information for the dnf package manager and how it is configured for local system. By default, this plugin will collect configuration files from /etc/dnf, repo files defined in /etc/yum.repos.d/, module information, and various 'dnf list' commands. When using the 'history-info' option, detailed transaction information will be collected for the most recent 50 dnf transactions, and will be saved to the sos_commands/dnf/history-info directory. """ short_desc = 'dnf package manager' plugin_name = "dnf" profiles = ('system', 'packagemanager', 'sysmgmt') files = ('/etc/dnf/dnf.conf',) packages = ('dnf',) option_list = [ PluginOpt('history-info', default=False, desc='collect detailed transaction history') ] def get_modules_info(self, modules): """ Get DN module information """ if not modules: return # take just lines with the module names, i.e. containing "[i]" and # not the "Hint: [d]efault, [e]nabled, [i]nstalled,.." for line in modules.splitlines(): if "[i]" in line: module = line.split()[0] if module != "Hint:": self.add_cmd_output("dnf module info " + module, tags='dnf_module_info') def setup(self): self.add_file_tags({ '/etc/dnf/modules.d/.*.module': 'dnf_modules' }) self.add_copy_spec([ "/etc/dnf/", "/etc/yum.conf", "/etc/yum/pluginconf.d/", "/etc/yum/vars/", ]) self.add_copy_spec("/etc/yum.repos.d/", tags=['yum_repos_d', 'dnf_repos_d', 'dnf_repo']) if self.get_option("all_logs"): self.add_copy_spec("/var/log/dnf.*") else: self.add_copy_spec("/var/log/dnf.log*") self.add_copy_spec("/var/log/dnf.librepo.log*") self.add_copy_spec("/var/log/dnf.rpm.log*") self.add_cmd_output("dnf module list", tags='dnf_module_list') self.add_cmd_output([ "dnf --version", "dnf list extras", "package-cleanup --dupes", "package-cleanup --problems" ]) self.add_cmd_output("dnf list installed", tags=["yum_list_installed", "dnf_list_installed"]) self.add_cmd_output('dnf -C repolist', tags=['yum_repolist', 'dnf_repolist']) self.add_cmd_output('dnf -C repolist --verbose') self.add_forbidden_path([ "/etc/pki/entitlement/key.pem", "/etc/pki/entitlement/*-key.pem" ]) self.add_copy_spec([ "/etc/pki/product/*.pem", "/etc/pki/consumer/cert.pem", "/etc/pki/entitlement/*.pem" ]) if not self.get_option("history-info"): self.add_cmd_output("dnf history", tags='dnf_history') else: history = self.collect_cmd_output("dnf history", tags='dnf_history') transactions = -1 if history['output']: for line in history['output'].splitlines(): try: transactions = int(line.split('|')[0].strip()) break except ValueError: # not a valid line to extract transactions from, ignore pass for tr_id in range(1, min(transactions+1, 50)): self.add_cmd_output(f"dnf history info {tr_id}", subdir="history-info", tags='dnf_history_info') # Get list of dnf installed modules and their details. module_cmd = "dnf module list --installed" modules = self.collect_cmd_output(module_cmd) self.get_modules_info(modules['output']) def postproc(self): # Scrub passwords in repositories and yum/dnf variables # Example of scrubbing: # # password=hackme # To: # password=******** # # Whitespace around '=' is allowed. regexp = r"(password(\s)*=(\s)*)(\S+)\n" repl = r"\1********\n" for file in ["/etc/yum.repos.d/*", "/etc/dnf/vars/*"]: self.do_path_regex_sub(file, regexp, repl) # Scrub password and proxy_password from /etc/dnf/dnf.conf. # This uses the same regex patterns as above. # # Example of scrubbing: # # proxy_password = hackme # To: # proxy_password = ******** # self.do_file_sub("/etc/dnf/dnf.conf", regexp, repl) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/npm.py0000664000175000017500000000407414660147624015740 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Tomas Tomecek # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Npm(Plugin, IndependentPlugin): short_desc = 'Information from available npm modules' plugin_name = 'npm' profiles = ('system',) option_list = [ PluginOpt('project-path', default='', val_type=str, desc='Collect npm modules of project at this path') ] # in Fedora, Debian, Ubuntu and Suse the package is called npm packages = ('npm',) def _get_npm_output(self, cmd, filename, working_directory=None): # stderr output is already part of the json, key "problems" self.add_cmd_output( cmd, suggest_filename=filename, stderr=False, runat=working_directory ) def setup(self): if self.get_option("project-path"): project_path = os.path.abspath(os.path.expanduser( self.get_option("project-path"))) self._get_npm_output("npm ls --json", "npm_ls_project", working_directory=project_path) self._get_npm_output("npm config list -l", "npm_config_list_project", working_directory=project_path) self._get_npm_output("npm ls -g --json", "npm_ls_global") self._get_npm_output("npm config list -l", "npm_config_list_global") class NpmViaNodeJS(Npm): """ some distribution methods don't provide 'npm' via npm package """ # upstream doesn't have an npm package, it's just nodejs # also in Fedora 24+ it is just nodejs, no npm package packages = ('nodejs', ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nvmetcli.py0000664000175000017500000000172114660147624016763 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Nitin Yewale # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class NvmetCli(Plugin, IndependentPlugin): short_desc = 'Collect config and system information for nvmetcli' packages = ('nvmetcli', ) profiles = ('storage', ) plugin_name = 'nvmetcli' def setup(self): self.add_cmd_output([ "nvmetcli ls" ]) self.add_journal(units=["nvme", "nvmet", "nvmet_rdma"]) self.add_copy_spec([ "/sys/kernel/config/nvmet", "/etc/nvmet/config.json", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dellrac.py0000664000175000017500000000330114660147624016544 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class DellRAC(Plugin, IndependentPlugin): short_desc = 'Dell Remote Access Controller Administration' plugin_name = 'dellrac' profiles = ('system', 'storage', 'hardware',) packages = ('srvadmin-idracadm7',) option_list = [ PluginOpt('debug', default=False, desc='capture support assist data') ] racadm = '/opt/dell/srvadmin/bin/idracadm7' prefix = 'idracadm7' def setup(self): for subcmd in ['getniccfg', 'getsysinfo']: self.add_cmd_output( f'{self.racadm} {subcmd}', suggest_filename=f'{self.prefix}_{subcmd}') if self.get_option("debug"): self.do_debug() def do_debug(self): """ Ensure the sos_commands/dellrac directory does exist in either case as we will need to run the command at that dir, and also ensure logpath is properly populated in either case as well. """ try: logpath = self.get_cmd_output_path() except FileExistsError: logpath = self.get_cmd_output_path(make=False) subcmd = 'supportassist collect -f' self.add_cmd_output( f'{self.racadm} {subcmd} support.zip', runat=logpath, suggest_filename=f'{self.prefix}_{subcmd}') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/autofs.py0000664000175000017500000000543014660147624016444 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin, DebianPlugin class Autofs(Plugin): short_desc = 'Autofs on-demand automounter' plugin_name = "autofs" profiles = ('storage', 'nfs') files = ('/etc/sysconfig/autofs', '/etc/default/autofs') packages = ('autofs',) def checkdebug(self): """ testing if autofs debug has been enabled anywhere """ # Global debugging opt = self.file_grep(r"^(DEFAULT_LOGGING|DAEMONOPTIONS)=(.*)", *self.files) for opt1 in opt: for opt2 in opt1.split(" "): if opt2 in ("--debug", "debug"): return True return False def getdaemondebug(self): """ capture daemon debug output """ debugout = self.file_grep(r"^(daemon.*)\s+(\/var\/log\/.*)", *self.files) for i in debugout: return i[1] return None def setup(self): self.add_copy_spec("/etc/auto*") self.add_file_tags({"/etc/autofs.conf": "autofs_conf"}) self.add_service_status("autofs") self.add_cmd_output("automount -m") if self.checkdebug(): self.add_copy_spec(self.getdaemondebug()) def postproc(self): self.do_path_regex_sub( "/etc/auto*", r"(password=)[^,\s]*", r"\1********" ) # Hide secrets in the LDAP authentication config # # Example of scrubbing of the secret: # # secret="abc" # or # encoded_secret = 'abc' # # to: # # secret="********" # or # encoded_secret = '********' # self.do_file_sub( "/etc/autofs_ldap_auth.conf", r"(secret[\s]*[=]+[\s]*)(\'|\").*(\'|\")", r"\1\2********\3" ) self.do_cmd_output_sub( "automount -m", r"(password=)[^,\s]*", r"\1********" ) class RedHatAutofs(Autofs, RedHatPlugin): def setup(self): super().setup() if self.get_option("verify"): self.add_cmd_output("rpm -qV autofs") class DebianAutofs(Autofs, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_cmd_output("dpkg-query -s autofs") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/snapper.py0000664000175000017500000000134514660147624016614 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Snapper(Plugin, IndependentPlugin): short_desc = 'System snapper' plugin_name = 'snapper' commands = ("snapper",) def setup(self): self.add_cmd_output([ "snapper --version", "snapper list" ]) self.add_dir_listing('/usr/lib/snapper/') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_glance.py0000664000175000017500000001126114660147624020442 0ustar arifarif# Copyright (C) 2013 Red Hat, Inc., Flavio Percoco # Copyright (C) 2012 Rackspace US, Inc., # Justin Shepherd # Copyright (C) 2009 Red Hat, Inc., Joey Boggs # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackGlance(Plugin): short_desc = 'OpenStack Glance' plugin_name = "openstack_glance" profiles = ('openstack', 'openstack_controller') containers = ('glance_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/glance_api" service_name = "openstack-glance-api.service" def setup(self): if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/glance/", ]) else: self.add_copy_spec([ "/var/log/glance/*.log", ]) self.add_copy_spec([ "/etc/glance/", self.var_puppet_gen + "/etc/glance/", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf" ]) # collect commands output only if the openstack-glance-api service # is running in_container = self.container_exists('glance_api') if self.is_service_running(self.service_name) or in_container: glance_config = "" # if containerized we need to pass the config to the cont. if in_container: glance_config = "--config-dir " + self.var_puppet_gen + \ "/etc/glance/" self.add_cmd_output( "glance-manage " + glance_config + " db_version", suggest_filename="glance_db_version" ) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. " "Source the environment file for the user " "intended to connect to the OpenStack " "environment.") else: res = self.collect_cmd_output( "openstack image list --long" ) if res['status'] == 0: glance_images = res['output'] for image in glance_images.splitlines()[3:-1]: image = image.split()[1] cmd = f"openstack image show {image}" self.add_cmd_output(cmd) self.add_file_tags({ "/etc/glance/glance-api.conf": "glance_api_conf", "/etc/glance/glance-cache.conf": "glance_cache_conf", "/etc/glance/glance-registry.conf": "glance_registry_conf", "/var/log/glance/api.log": "glance_api_log" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/glance/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/glance/*", regexp, subst ) def postproc(self): protect_keys = [ "admin_password", "password", "qpid_password", "rabbit_password", "s3_store_secret_key", "ssl_key_password", "vmware_server_password", "transport_url", "memcache_secret_key" ] connection_keys = ["connection"] self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianGlance(OpenStackGlance, DebianPlugin, UbuntuPlugin): packages = ( 'glance', 'glance-api', 'glance-client', 'glance-common', 'glance-registry', 'python-glance', 'python3-glance', ) service_name = 'apache2.service' class RedHatGlance(OpenStackGlance, RedHatPlugin): packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_neutron.py0000664000175000017500000001445414660147624020712 0ustar arifarif# Copyright (C) 2013 Red Hat, Inc., Brent Eagles # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackNeutron(Plugin): short_desc = 'OpenStack Networking' plugin_name = "openstack_neutron" profiles = ('openstack', 'openstack_controller', 'openstack_compute', 'openstack_edpm') var_puppet_gen = "/var/lib/config-data/puppet-generated/neutron" def setup(self): if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/neutron/", ]) else: self.add_copy_spec([ "/var/log/neutron/*.log", ]) self.add_copy_spec([ "/etc/neutron/", self.var_puppet_gen + "/etc/neutron/", self.var_puppet_gen + "/etc/default/neutron-server", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf" ]) # copy whole /var/lib/neutron except for potentially huge lock subdir; # rather take a list of files in the dir only self.add_copy_spec("/var/lib/neutron/") self.add_forbidden_path("/var/lib/neutron/lock") self.add_dir_listing('/var/lib/neutron/lock', recursive=True) if self.path_exists(self.var_puppet_gen): ml2_pre = self.var_puppet_gen else: ml2_pre = "" ml2_conf_file = f"{ml2_pre}/etc/neutron/plugins/ml2/ml2_conf.ini" ml2_certs = [] ml2_cert_keys = [ 'ovn_nb_private_key', 'ovn_nb_certificate', 'ovn_nb_ca_cert', 'ovn_sb_private_key', 'ovn_sb_certificate', 'ovn_sb_ca_cert', ] try: with open(ml2_conf_file, 'r', encoding='UTF-8') as cfile: for line in cfile.read().splitlines(): if not line: continue words = line.split('=') if words[0].strip() in ml2_cert_keys: ml2_certs.append(words[1].strip()) except IOError as error: self._log_error(f'Could not open conf file {ml2_conf_file}:' f' {error}') self.add_forbidden_path(ml2_certs) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. Source " "the environment file for the user intended " "to connect to the OpenStack environment.") else: cmds = [ "subnet", "port", "router", "network agent", "network", "extension", "floating ip", "security group", ] for cmd in cmds: res = self.collect_cmd_output(f"openstack {cmd} list") if res['status'] == 0: neutron_items = res['output'] for item in neutron_items.splitlines()[3:-1]: item = item.split()[1] show_cmd = f"openstack {cmd} show {item}" self.add_cmd_output(show_cmd) self.add_file_tags({ ".*/etc/neutron/plugins/ml2/ml2_conf.ini": "neutronml2_conf", "/var/log/neutron/server.log": "neutron_server_log" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/neutron/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/neutron/*", regexp, subst ) def postproc(self): protect_keys = [ "rabbit_password", "qpid_password", "nova_admin_password", "xenapi_connection_password", "password", "server_auth", "admin_password", "metadata_proxy_shared_secret", "eapi_password", "crd_password", "primary_l3_host_password", "serverauth", "ucsm_password", "ha_vrrp_auth_password", "ssl_key_password", "nsx_password", "vcenter_password", "edge_appliance_password", "tenant_admin_password", "apic_password", "transport_url", "memcache_secret_key" ] connection_keys = ["connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianNeutron(OpenStackNeutron, DebianPlugin, UbuntuPlugin): packages = ( 'neutron-common', 'neutron-plugin-cisco', 'neutron-plugin-linuxbridge-agent', 'neutron-plugin-nicira', 'neutron-plugin-openvswitch', 'neutron-plugin-openvswitch-agent', 'neutron-plugin-ryu', 'neutron-plugin-ryu-agent', 'neutron-server', 'python-neutron', 'python3-neutron', ) def check_enabled(self): return self.is_installed("neutron-common") def setup(self): super().setup() self.add_copy_spec("/etc/sudoers.d/neutron_sudoers") class RedHatNeutron(OpenStackNeutron, RedHatPlugin): packages = ('openstack-selinux',) var_ansible_gen = "/var/lib/config-data/ansible-generated/" def setup(self): super().setup() self.add_copy_spec([ "/etc/sudoers.d/neutron-rootwrap", self.var_ansible_gen + "/neutron-dhcp-agent/", self.var_ansible_gen + "/neutron-dhcp-ovn/", self.var_ansible_gen + "/neutron-sriov-agent/" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ansible.py0000664000175000017500000000205514660147624016560 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class Ansible(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'Ansible configuration management' plugin_name = 'ansible' profiles = ('system',) packages = ( 'ansible', 'ansible1.9' ) def setup(self): self.add_copy_spec("/etc/ansible/") self.add_cmd_output([ "ansible all -m ping -vvvv", "ansible --version" ]) # don't generic & collect potentially sensitive files and dirs self.add_forbidden_path([ "/etc/ansible/facts.d/", "/etc/ansible/roles/", "/etc/ansible/hosts", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/i18n.py0000664000175000017500000000140614660147624015721 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class I18n(Plugin, IndependentPlugin): short_desc = 'Internationalization' plugin_name = 'i18n' profiles = ('system',) def setup(self): self.add_copy_spec([ "/etc/X11/xinit/xinput.d/*", "/etc/locale.conf" ]) self.add_cmd_output("locale", env={'LC_ALL': None}, tags="locale") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_mistral.py0000664000175000017500000000315214660147624020664 0ustar arifarif# Copyright (C) 2022 Red Hat, Inc. # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin MISTRAL_DIRECTORIES = [ '/var/log/mistral/', '/var/lib/mistral/', ] MISTRAL_LOGS = [ '/var/log/mistral/*.log', '/var/lib/mistral/*/*.log', ] class OpenStackMistral(Plugin, RedHatPlugin): '''Gather Mistral directories content, both data from /var/lib/mistral and its log from /var/log/mistral if it exists (older OSP). The data also embed logs for the ansible runs launched via the service, meaning we'll be able to properly debug failures therein. The rest of the data are the generated environment files, also really useful in order to debug an issue at deploy or day-2 operations. We filter out on the presence of any "mistral" related container on the host - usually the Undercloud presents mistral_engine, mistral_executor and mistral_api. ''' short_desc = 'OpenStack Mistral' plugin_name = "openstack_mistral" profiles = ('openstack', 'openstack_undercloud') containers = ('.*mistral_engine',) def setup(self): if self.get_option('all_log'): self.add_copy_spec(MISTRAL_DIRECTORIES) else: self.add_copy_spec(MISTRAL_LOGS) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/docker.py0000664000175000017500000001200214660147624016403 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, UbuntuPlugin, SoSPredicate, CosPlugin, PluginOpt, DebianPlugin) class Docker(Plugin, CosPlugin): short_desc = 'Docker containers' plugin_name = 'docker' profiles = ('container',) option_list = [ PluginOpt('all', default=False, desc='collect for all containers, even terminated ones'), PluginOpt('logs', default=False, desc='collect stdout/stderr logs for containers'), PluginOpt('size', default=False, desc='collect image sizes for docker ps') ] def setup(self): self.add_copy_spec([ "/etc/docker/daemon.json", "/var/lib/docker/repositories-*" ]) self.add_env_var([ 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'ALL_PROXY', 'DOCKER_BUILD_PROXY', 'DOCKER_RUN_PROXY' ]) self.add_journal(units="docker") self.add_dir_listing('/etc/docker', recursive=True) self.set_cmd_predicate(SoSPredicate(self, services=["docker"])) subcmds = [ 'events --since 24h --until 1s', 'ps', 'stats --no-stream', 'version', 'volume ls' ] for subcmd in subcmds: self.add_cmd_output(f"docker {subcmd}") self.add_cmd_output("docker info", tags="docker_info") self.add_cmd_output("docker images", tags="docker_images") self.add_cmd_output("docker ps -a", tags="docker_list_containers") # separately grab these separately as they can take a *very* long time if self.get_option('size'): self.add_cmd_output('docker ps -as', priority=100) self.add_cmd_output('docker system df', priority=100) nets = self.collect_cmd_output('docker network ls') if nets['status'] == 0: networks = [n.split()[1] for n in nets['output'].splitlines()[1:]] for net in networks: self.add_cmd_output(f"docker network inspect {net}") containers = [ c[0] for c in self.get_containers(runtime='docker', get_all=self.get_option('all')) ] images = self.get_container_images(runtime='docker') volumes = self.get_container_volumes(runtime='docker') for container in containers: self.add_cmd_output(f"docker inspect {container}", subdir='containers') if self.get_option('logs'): self.add_cmd_output(f"docker logs -t {container}", subdir='containers') for img in images: name, img_id = img insp = name if 'none' not in name else img_id self.add_cmd_output(f"docker inspect {insp}", subdir='images', tags="docker_image_inspect") for vol in volumes: self.add_cmd_output(f"docker volume inspect {vol}", subdir='volumes') def postproc(self): # Attempts to match key=value pairs inside container inspect output # for potentially sensitive items like env vars that contain passwords. # Typically, these will be seen in env elements or similar, and look # like this: # "Env": [ # "mypassword=supersecret", # "container=oci" # ], # This will mask values when the variable name looks like it may be # something worth obfuscating. env_regexp = r'(?P(pass|key|secret|PASS|KEY|SECRET).*?)=' \ '(?P.*?)"' self.do_cmd_output_sub('*inspect*', env_regexp, r'\g=********"') class RedHatDocker(Docker, RedHatPlugin): packages = ('docker', 'docker-latest', 'docker-io', 'docker-engine', 'docker-ce', 'docker-ee') def setup(self): super().setup() self.add_copy_spec([ "/etc/udev/rules.d/80-docker.rules", "/etc/containers/" ]) class UbuntuDocker(Docker, UbuntuPlugin, DebianPlugin): packages = ('docker.io', 'docker-engine', 'docker-ce', 'docker-ee') def setup(self): super().setup() self.add_copy_spec([ "/etc/default/docker", "/run/docker/libcontainerd/containerd/events.log" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/foreman_openscap.py0000664000175000017500000000147614660147624020470 0ustar arifarif# Copyright (C) 2023 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class ForemanOpenSCAP(Plugin, IndependentPlugin): short_desc = 'Foreman OpenSCAP client' plugin_name = 'foreman_openscap' profiles = ('sysmgmt',) packages = ('rubygem-foreman_scap_client', 'ruby-foreman-scap-client') def setup(self): self.add_copy_spec("/etc/foreman_scap_client/config.yaml") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_swift.py0000664000175000017500000000571114660147624020350 0ustar arifarif# Copyright (C) 2013 Red Hat, Inc., Flavio Percoco # Copyright (C) 2012 Rackspace US, Inc., # Justin Shepherd # Copyright (C) 2009 Red Hat, Inc., Joey Boggs # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackSwift(Plugin): short_desc = 'OpenStack Swift' plugin_name = "openstack_swift" profiles = ('openstack', 'openstack_controller') var_puppet_gen = "/var/lib/config-data/puppet-generated" def setup(self): if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/swift/", ]) else: self.add_copy_spec([ "/var/log/swift/*.log", ]) self.add_copy_spec([ "/etc/swift/", self.var_puppet_gen + "/swift/etc/*", self.var_puppet_gen + "/swift/etc/swift/*", self.var_puppet_gen + "/swift/etc/xinetd.d/*", self.var_puppet_gen + "/memcached/etc/sysconfig/memcached" ]) self.add_file_tags({ "/etc/swift/swift.conf": "swift_conf", "/var/log/swift/swift.log": "swift_log" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub(r"/etc/swift/.*\.conf.*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + r"/swift/etc/swift/.*\.conf.*", regexp, subst ) def postproc(self): protect_keys = [ "ldap_dns_password", "neutron_admin_password", "rabbit_password", "qpid_password", "powervm_mgr_passwd", "virtual_power_host_pass", "xenapi_connection_password", "password", "host_password", "vnc_password", "admin_password", "transport_url" ] connection_keys = ["connection", "sql_connection"] self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) join_con_keys = '|'.join(connection_keys) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianSwift(OpenStackSwift, DebianPlugin, UbuntuPlugin): packages = ( 'swift', 'swift-account', 'swift-container', 'swift-object', 'swift-proxy', 'swauth', 'python-swift', 'python-swauth', 'python3-swift', ) class RedHatSwift(OpenStackSwift, RedHatPlugin): packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovirt_imageio.py0000664000175000017500000000307414660147624020002 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Sandro Bonazzola # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OvirtImageIO(Plugin, RedHatPlugin): short_desc = 'oVirt Image I/O Daemon / Proxy' packages = ( 'ovirt-imageio-daemon', 'ovirt-imageio-proxy', ) plugin_name = 'ovirt_imageio' profiles = ('virt',) def setup(self): all_logs = self.get_option('all_logs') # Add configuration files self.add_copy_spec([ '/etc/ovirt-imageio-daemon/logger.conf', '/etc/ovirt-imageio-daemon/daemon.conf', '/etc/ovirt-imageio-proxy/ovirt-imageio-proxy.conf', '/etc/ovirt-imageio/conf.d/*.conf', ]) if all_logs: logs = ['/var/log/ovirt-imageio-proxy/image-proxy.log*', '/var/log/ovirt-imageio-daemon/daemon.log*', '/var/log/ovirt-imageio/daemon.log*'] else: logs = ['/var/log/ovirt-imageio-proxy/image-proxy.log', '/var/log/ovirt-imageio-daemon/daemon.log', '/var/log/ovirt-imageio/daemon.log'] # Add log files self.add_copy_spec(logs) # vim: expandtab tabstop=4 shiftwidth=4 sos-4.8.0/sos/report/plugins/salt.py0000664000175000017500000000422014660147624016102 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin class Salt(Plugin, IndependentPlugin): short_desc = 'Salt' plugin_name = 'salt' profiles = ('sysmgmt',) packages = ('salt', 'salt-minion', 'venv-salt-minion', 'salt-common',) def setup(self): all_logs = self.get_option("all_logs") if not all_logs: self.add_copy_spec("/var/log/salt/minion") else: self.add_copy_spec("/var/log/salt") self.add_copy_spec([ "/var/log/venv-salt-minion.log", "/var/log/salt-ssh.log", ]) self.add_copy_spec([ "/etc/salt", "/etc/venv-salt-minion/", "/usr/local/etc/salt", ]) self.add_forbidden_path([ "/etc/salt/pki/*/*.pem", "/etc/venv-salt-minion/pki/*/*.pem", "/usr/local/etc/salt/pki/*/*.pem", ]) self.add_cmd_output([ "systemctl --full status salt-minion", "systemctl --full status venv-salt-minion", "salt-minion --versions-report", "venv-salt-minion --versions-report", "salt-call --local grains.items --out yaml", "venv-salt-call --local grains.items --out yaml", ], timeout=30) def postproc(self): regexp = r'(^\s+.*(pass|secret|(? # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # pylint: disable=too-many-locals,too-many-branches """ This exports methods available for use by plugins for sos """ import contextlib import os import glob import re import stat from time import time import logging import fnmatch import errno import textwrap from datetime import datetime from sos.utilities import (sos_get_command_output, import_module, grep, fileobj, tail, is_executable, TIMEOUT_DEFAULT, path_exists, path_isdir, path_isfile, path_islink, listdir, path_join, bold, file_is_binary, recursive_dict_values_by_key) from sos.archive import P_FILE, P_LINK def regex_findall(regex, fname): """Return a list of all non overlapping matches in the string(s)""" try: with fileobj(fname) as f: return re.findall(regex, f.read(), re.MULTILINE) except AttributeError: return [] def _mangle_command(command, name_max): mangledname = re.sub(r"^/(usr/|)(bin|sbin)/", "", command) mangledname = re.sub(r"[^\w\-\.\/]+", "_", mangledname) mangledname = re.sub(r"/", ".", mangledname).strip(" ._-") mangledname = mangledname[0:name_max] return mangledname def _node_type(st): """ return a string indicating the type of special node represented by the stat buffer st (block, character, fifo, socket). """ _types = [ (stat.S_ISBLK, "block device"), (stat.S_ISCHR, "character device"), (stat.S_ISFIFO, "named pipe"), (stat.S_ISSOCK, "socket") ] for t in _types: if t[0](st.st_mode): return t[1] return '' _certmatch = re.compile("----(?:-| )BEGIN.*?----(?:-| )END", re.DOTALL) _cert_replace = "-----SCRUBBED" class SoSPredicate: """A class to implement collection predicates. A predicate gates the collection of data by an sos plugin. For any `add_cmd_output()`, `add_copy_spec()` or `add_journal()` call, the passed predicate will be evaulated and collection will proceed if the result is `True`, and not otherwise. Predicates may be used to control conditional data collection without the need for explicit conditional blocks in plugins. :param owner: The ``Plugin`` object creating the predicate :type owner: ``Plugin`` :param dry_run: Is sos running in dry_run mode? :type dry_run: ``bool`` :param kmods: Kernel module name(s) to check presence of :type kmods: ``list``, or ``str`` of single name :param services: Service name(s) to check if running :type services: ``list``, or ``str`` of single name :param packages: Package name(s) to check presence of :type packages: ``list``, or ``str`` of single name :param cmd_outputs: Command to run, with output string to check :type cmd_outputs: ``list`` of ``dict``s, or single ``dict`` taking form {'cmd': , 'output': } :param arch: Architecture(s) that the local system is matched against :type arch: ``list``, or ``str`` of single architecture :param required: For each parameter provided, should the checks require all items, no items, or any items provided :type required: ``dict``, with keys matching parameter names and values being either 'any', 'all', or 'none. Default 'any'. """ #: The plugin that owns this predicate _owner = None #: Skip all collection? dry_run = False #: Kernel module enablement list kmods = [] #: Services enablement list services = [] #: Package presence list packages = [] # Command output inclusion pairs {'cmd': 'foo --help', 'output': 'bar'} cmd_outputs = [] #: Allowed architecture(s) of the system arch = [] def __str(self, quote=False, prefix="", suffix=""): """Return a string representation of this SoSPredicate with optional prefix, suffix and value quoting. """ quotes = '"%s"' pstr = f"dry_run={self.dry_run}, " kmods = self.kmods kmods = [quotes % k for k in kmods] if quote else kmods pstr += f"kmods=[{','.join(kmods)}], " services = self.services services = [quotes % s for s in services] if quote else services pstr += f"services=[{','.join(services)}], " pkgs = self.packages pkgs = [quotes % p for p in pkgs] if quote else pkgs pstr += f"packages=[{','.join(pkgs)}], " cmdoutputs = [ f"{{ {quotes % 'cmd'}: {quotes % cmdoutput['cmd']}, " f"{quotes % 'output'}: {quotes % cmdoutput['output']} }}" for cmdoutput in self.cmd_outputs ] pstr += f"cmdoutputs=[{','.join(cmdoutputs)}], " arches = self.arch arches = [quotes % a for a in arches] if quote else arches pstr += f"arches=[{','.join(arches)}]" return prefix + pstr + suffix def __str__(self): """Return a string representation of this SoSPredicate. "dry_run=False, kmods=[], services=[], cmdoutputs=[]" """ return self.__str() def __repr__(self): """Return a machine readable string representation of this SoSPredicate. "SoSPredicate(dry_run=False, kmods=[], services=[], cmdoutputs=[])" """ return self.__str(quote=True, prefix="SoSPredicate(", suffix=")") def _check_required_state(self, items, required): """Helper to simplify checking the state of the predicate's evaluations against the setting of the required state of that evaluation """ if required == 'any': return any(items) if required == 'all': return all(items) if required == 'none': return not any(items) raise ValueError( f"predicate requires must be 'any', 'all', or 'none' " f"not {required}" ) def _failed_or_forbidden(self, test, item): """Helper to direct failed predicates to provide the proper messaging based on the required check type :param test: The type of check we're doing, e.g. kmods, arch :param item: The string of what failed """ _req = self.required[test] if _req != 'none': self._failed[test].append(item) else: self._forbidden[test].append(item) def _eval_kmods(self): if not self.kmods or self._owner.get_option('allow_system_changes'): return True _kmods = [] # Are kernel modules loaded? for kmod in self.kmods: res = self._owner.is_module_loaded(kmod) _kmods.append(res) if not res: self._failed_or_forbidden('kmods', kmod) return self._check_required_state(_kmods, self.required['kmods']) def _eval_services(self): if not self.services: return True _svcs = [] for svc in self.services: res = self._owner.is_service_running(svc) _svcs.append(res) if not res: self._failed_or_forbidden('services', svc) return self._check_required_state(_svcs, self.required['services']) def _eval_packages(self): if not self.packages: return True _pkgs = [] for pkg in self.packages: res = self._owner.is_installed(pkg) _pkgs.append(res) if not res: self._failed_or_forbidden('packages', pkg) return self._check_required_state(_pkgs, self.required['packages']) def _eval_cmd_output(self, cmd_output): """Does 'cmd' output contain string 'output'?""" if 'cmd' not in cmd_output or 'output' not in cmd_output: return False result = sos_get_command_output(cmd_output['cmd']) if result['status'] != 0: return False for line in result['output'].splitlines(): if cmd_output['output'] in line: return True return False def _eval_cmd_outputs(self): if not self.cmd_outputs: return True _cmds = [] for cmd in self.cmd_outputs: res = self._eval_cmd_output(cmd) _cmds.append(res) if not res: self._failed_or_forbidden( 'cmd_outputs', f"{cmd['cmd']}: {cmd['output']}" ) return self._check_required_state(_cmds, self.required['cmd_outputs']) def _eval_arch(self): if not self.arch: return True # a test for 'all' against arch does not make sense, so only test to # see if the system's reported architecture is in the last of 'allowed' # arches requested by the predicate _arch = self._owner.policy.get_arch() regex = f'(?:{"|".join(self.arch)})' if self.required['arch'] == 'none': if re.match(regex, _arch): self._forbidden['architecture'].append(_arch) return False return True if re.match(regex, _arch): return True self._failed['architecture'].append(_arch) return False def _report_failed(self): """Return a string informing user what caused the predicate to fail evaluation """ msg = '' _substr = "required %s missing: %s." for key, val in self._failed.items(): if not val: continue val = set(val) msg += _substr % (key, ', '.join(v for v in val)) return msg def _report_forbidden(self): """Return a string informing the user that a forbidden condition exists which caused the predicate to fail """ msg = '' _substr = "forbidden %s '%s' found." for key, val in self._forbidden.items(): if not val: continue val = set(val) msg += _substr % (key, ', '.join(v for v in val)) return msg def report_failure(self): """Used by `Plugin()` to obtain the error string based on if the reason was a failed check or a forbidden check """ msg = [ self._report_failed(), self._report_forbidden(), '(dry run)' if self.dry_run else '' ] return " ".join(msg).lstrip() def __bool__(self): """Predicate evaluation hook. """ # Null predicate? if not any([self.kmods, self.services, self.packages, self.cmd_outputs, self.arch, self.dry_run]): return True return ((self._eval_kmods() and self._eval_services() and self._eval_packages() and self._eval_cmd_outputs() and self._eval_arch()) and not self.dry_run) def __init__(self, owner, dry_run=False, kmods=[], services=[], packages=[], cmd_outputs=[], arch=[], required={}): """Initialise a new SoSPredicate object """ self._owner = owner self.kmods = list(kmods) self.services = list(services) self.packages = list(packages) self.arch = list(arch) if not isinstance(cmd_outputs, list): cmd_outputs = [cmd_outputs] self.cmd_outputs = cmd_outputs self.dry_run = dry_run | self._owner.commons['cmdlineopts'].dry_run self.required = {'kmods': 'any', 'services': 'any', 'packages': 'any', 'cmd_outputs': 'any', 'arch': 'any'} self.required.update({ k: v for k, v in required.items() if required[k] != self.required[k] }) #: Dict holding failed evaluations self._failed = { 'kmods': [], 'services': [], 'packages': [], 'cmd_outputs': [], 'architecture': [] } self._forbidden = { 'kmods': [], 'services': [], 'packages': [], 'cmd_outputs': [], 'architecture': [] } class SoSCommand: """A class to represent a command to be collected. A SoSCommand() object is instantiated for each command handed to an _add_cmd_output() call, so that we no longer need to pass around a very long tuple to handle the parameters. Any option supported by _add_cmd_output() is passed to the SoSCommand object and converted to an attribute. SoSCommand.__dict__ is then passed to _get_command_output_now() for each command to be collected. """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __str__(self): """Return a human readable string representation of this SoSCommand """ return ', '.join(f"{param}={val}" for (param, val) in sorted(self.__dict__.items())) class PluginOpt(): """This is used to define options available to plugins. Plugins will need to define options alongside their distro-specific classes in order to add support for user-controlled changes in Plugin behavior. :param name: The name of the plugin option :type name: ``str`` :param default: The default value of the option :type default: Any :param desc: A short description of the effect of the option :type desc: ``str`` :param long_desc: A detailed description of the option. Will be used by `sos info` :type long_desc: ``str`` :param val_type: The type of object the option accepts for values. If not provided, auto-detect from the type of ``default`` :type val_type: A single type or a ``list`` of types """ name = '' default = None enabled = False desc = '' long_desc = '' value = None val_type = [None] plugin = '' def __init__(self, name='undefined', default=None, desc='', long_desc='', val_type=None): self.name = name self.default = default self.desc = desc self.long_desc = long_desc self.value = self.default if val_type is not None: if not isinstance(val_type, list): val_type = [val_type] else: val_type = [default.__class__] self.val_type = val_type def __str__(self): items = [ f'name={self.name}', f'desc=\'{self.desc}\'', f'value={self.value}', f'default={self.default}' ] return '(' + ', '.join(items) + ')' def __repr__(self): return self.__str__() def set_value(self, val): # 'str' type accepts any value, incl. numbers if type('') in self.val_type: self.value = str(val) return if not any(isinstance(val, _t) for _t in self.val_type): valid = [] for t in self.val_type: if t is None: continue if t.__name__ == 'bool': valid.append("boolean true/false (on/off, etc)") elif t.__name__ == 'str': valid.append("string (no spaces)") elif t.__name__ == 'int': valid.append("integer values") raise Exception( f"Plugin option '{self.plugin}.{self.name}' takes " f"{', '.join(valid)}, not {type(val).__name__}" ) self.value = val class Plugin(): """This is the base class for sos report plugins. Plugins should subclass this and set the class variables where applicable. :param commons: A set of information that is shared internally so that plugins may access the same dataset. This is provided automatically by sos :type commons: ``dict`` Each `Plugin()` subclass should also subclass at least one tagging class, e.g. ``RedHatPlugin``, to support that distribution. If different distributions require different collections, each distribution should have its own subclass of the Plugin that also subclasses the tagging class for their respective distributions. :cvar plugin_name: The name of the plugin, will be returned by `name()` :vartype plugin_name: ``str`` :cvar packages: Package name(s) that, if installed, enable this plugin :vartype packages: ``tuple`` :cvar files: File path(s) that, if present, enable this plugin :vartype files: ``tuple`` :cvar commands: Executables that, if present, enable this plugin :vartype commands: ``tuple`` :cvar kernel_mods: Kernel module(s) that, if loaded, enable this plugin :vartype kernel_mods: ``tuple`` :cvar services: Service name(s) that, if running, enable this plugin :vartype services: ``tuple`` :cvar architectures: Architecture(s) this plugin is enabled for. Defaults to 'none' to enable on all arches. :vartype architectures: ``tuple``, or ``None`` :cvar profiles: Name(s) of profile(s) this plugin belongs to :vartype profiles: ``tuple`` :cvar plugin_timeout: Timeout in seconds for this plugin as a whole :vartype plugin_timeout: ``int`` :cvar cmd_timeout: Timeout in seconds for individual commands :vartype cmd_timeout: ``int`` """ plugin_name = None packages = () files = () commands = () kernel_mods = () services = () containers = () architectures = None archive = None profiles = () sysroot = '/' plugin_timeout = TIMEOUT_DEFAULT cmd_timeout = TIMEOUT_DEFAULT _timeout_hit = False cmdtags = {} filetags = {} option_list = [] is_snap = False # Default predicates predicate = None cmd_predicate = None short_desc = "" def __init__(self, commons): self.copied_files = [] self.executed_commands = [] self._env_vars = set() self.alerts = [] self.custom_text = "" self.commons = commons self.forbidden_paths = [] self.copy_paths = set() self.container_copy_paths = [] self.copy_strings = [] self.collect_cmds = [] self.options = {} self.sysroot = commons['sysroot'] self.policy = commons['policy'] self.devices = commons['devices'] self.manifest = None self.skip_files = commons['cmdlineopts'].skip_files self.skip_commands = commons['cmdlineopts'].skip_commands self.default_environment = {} self._tail_files_list = [] self.soslog = self.commons['soslog'] if 'soslog' in self.commons \ else logging.getLogger('sos') # add the default plugin opts self.options.update(self.get_default_plugin_opts()) for popt in self.options: # pylint: disable=consider-using-dict-items self.options[popt].plugin = self.name() for opt in self.option_list: opt.plugin = self.name() self.options[opt.name] = opt # Check if any of the packages tuple is a snap self.is_snap = any( self.is_snap_installed(pkg) for pkg in list(self.packages) ) # Initialise the default --dry-run predicate self.set_predicate(SoSPredicate(self)) def get_default_plugin_opts(self): return { 'timeout': PluginOpt( 'timeout', default=-1, val_type=int, desc='Timeout in seconds for plugin to finish all collections' ), 'cmd-timeout': PluginOpt( 'cmd-timeout', default=-1, val_type=int, desc='Timeout in seconds for individual commands to finish' ), 'postproc': PluginOpt( 'postproc', default=True, val_type=bool, desc='Enable post-processing of collected data' ) } def set_plugin_manifest(self, manifest): """Pass in a manifest object to the plugin to write to :param manifest: The manifest that the plugin will add metadata to :type manifest: ``SoSManifest`` """ self.manifest = manifest # add these here for organization when they actually get set later self.manifest.add_field('start_time', '') self.manifest.add_field('end_time', '') self.manifest.add_field('run_time', '') self.manifest.add_field('setup_start', '') self.manifest.add_field('setup_end', '') self.manifest.add_field('setup_time', '') self.manifest.add_field('timeout', self.timeout) self.manifest.add_field('timeout_hit', False) self.manifest.add_field('command_timeout', self.cmdtimeout) self.manifest.add_list('commands', []) self.manifest.add_list('files', []) self.manifest.add_field('strings', {}) self.manifest.add_field('containers', {}) self.manifest.add_list('collections', []) def set_default_cmd_environment(self, env_vars): """ Specify a collection of environment variables that should always be passed to commands being executed by this plugin. :param env_vars: The environment variables and their values to set :type env_vars: ``dict{ENV_VAR_NAME: ENV_VAR_VALUE}`` """ if not isinstance(env_vars, dict): raise TypeError( "Environment variables for Plugin must be specified by dict" ) self.default_environment = env_vars self._log_debug("Default environment for all commands now set to " f"{self.default_environment}") def add_default_cmd_environment(self, env_vars): """ Add or modify a specific environment variable in the set of default environment variables used by this Plugin. :param env_vars: The environment variables to add to the current set of env vars in use :type env_vars: ``dict`` """ if not isinstance(env_vars, dict): raise TypeError("Environment variables must be added via dict") self._log_debug(f"Adding {env_vars} to default environment") self.default_environment.update(env_vars) def _get_cmd_environment(self, env=None): """ Get the merged set of environment variables for a command about to be executed by this plugin. :returns: The set of env vars to use for a command :rtype: ``dict`` """ if env is None: return self.default_environment if not isinstance(env, dict): raise TypeError("Command env vars must be passed as dict") _env = self.default_environment.copy() _env.update(env) return _env def timeout_from_options(self, optname, plugoptname, default_timeout): """ Get the timeout value for either the plugin or a command, as determined by either the value provided via the plugin.timeout or plugin.cmd-timeout option, the global timeout or cmd-timeout options, or the default value set by the plugin or the collection, in that order of precendence. :param optname: The name of the cmdline option being checked, either 'plugin_timeout' or 'timeout' :type optname: ``str`` :param plugoptname: The name of the plugin option name being checked, either 'timeout' or 'cmd-timeout' :type plugoptname: ``str`` :param default_timeout: The timeout to default to if determination is inconclusive or hits an error :type default_timeout: ``int`` :returns: The timeout value in seconds :rtype: ``int`` """ _timeout = None try: opt_timeout = self.get_option(optname) own_timeout = int(self.get_option(plugoptname)) if opt_timeout is None: _timeout = own_timeout elif opt_timeout is not None and own_timeout == -1: if opt_timeout == TIMEOUT_DEFAULT: _timeout = default_timeout else: _timeout = int(opt_timeout) elif opt_timeout is not None and own_timeout > -1: _timeout = own_timeout else: return None except ValueError: return default_timeout # Default to known safe value if _timeout is not None and _timeout > -1: return _timeout return default_timeout @property def timeout(self): """Returns either the default plugin timeout value, the value as provided on the commandline via -k plugin.timeout=value, or the value of the global --plugin-timeout option. """ _timeout = self.timeout_from_options('plugin_timeout', 'timeout', self.plugin_timeout) return _timeout @property def cmdtimeout(self): """Returns either the default command timeout value, the value as provided on the commandline via -k plugin.cmd-timeout=value, or the value of the global --cmd-timeout option. """ _cmdtimeout = self.timeout_from_options('cmd_timeout', 'cmd-timeout', self.cmd_timeout) return _cmdtimeout def set_timeout_hit(self): self._timeout_hit = True self.manifest.add_field('end_time', datetime.now()) self.manifest.add_field('timeout_hit', True) def check_timeout(self): """ Checks to see if the plugin has hit its timeout. This is set when the sos.collect_plugin() method hits a timeout and terminates the thread. From there, a Popen() call can still continue to run, and we need to manually terminate it. Thus, check_timeout() should only be called in sos_get_command_output(). Since sos_get_command_output() is not plugin aware, this method is handed to that call to use as a polling method, to avoid passing the entire plugin object. :returns: ``True`` if timeout has been hit, else ``False`` :rtype: ``bool`` """ return self._timeout_hit @classmethod def name(cls): """Get the name of the plugin :returns: The name of the plugin, in lowercase :rtype: ``str`` """ if cls.plugin_name: return cls.plugin_name return cls.__name__.lower() @classmethod def display_help(cls, section): if cls.plugin_name is None: cls.display_self_help(section) else: cls.display_plugin_help(section) @classmethod def display_plugin_help(cls, section): from sos.help import TERMSIZE section.set_title(f"{cls.plugin_name.title()} Plugin Information - " f"{cls.short_desc}") missing = '\nDetailed information is not available for this plugin.\n' # Concatenate the docstrings of distro-specific plugins with their # base classes, if available. try: _doc = '' _sc = cls.__mro__[1] if _sc != Plugin and _sc.__doc__: _doc = _sc.__doc__ if cls.__doc__: _doc += cls.__doc__ except Exception: _doc = None section.add_text(f'\n {_doc if _doc else missing}') if not any([cls.packages, cls.commands, cls.files, cls.kernel_mods, cls.services, cls.containers]): section.add_text("This plugin is always enabled by default.") else: for trig in ['packages', 'commands', 'files', 'kernel_mods', 'services']: if getattr(cls, trig, None): section.add_text( f"Enabled by {trig}: {', '.join(getattr(cls, trig))}", newline=False ) if getattr(cls, 'containers'): section.add_text( "Enabled by containers with names matching: " f"{', '.join(c for c in cls.containers)}", newline=False ) if cls.profiles: section.add_text( "Enabled with the following profiles: " f"{', '.join(p for p in cls.profiles)}", newline=False ) if hasattr(cls, 'verify_packages'): # pylint: disable=no-member section.add_text( "\nVerfies packages (when using --verify): " f"{', '.join(pkg for pkg in cls.verify_packages)}", newline=False, ) if cls.postproc is not Plugin.postproc: section.add_text( 'This plugin performs post-processing on potentially ' 'sensitive collections. Disabling post-processing may' ' leave sensitive data in plaintext.' ) if not cls.option_list: return optsec = section.add_section('Plugin Options') optsec.add_text( "These options may be toggled or changed using " f"'{bold(f'-k {cls.plugin_name}.option_name=$value')}'" ) optsec.add_text( bold((f"\n{' ':<4}{'Option Name':<20}{'Default':<30}" f"{'Description':<20}")), newline=False ) opt_indent = ' ' * 54 for opt in cls.option_list: _def = opt.default # convert certain values to text meanings if _def is None or _def == '': _def = "None/Unset" if isinstance(opt.default, bool): if opt.default: _def = "True/On" else: _def = "False/Off" _ln = f"{' ':<4}{opt.name:<20}{_def:<30}{opt.desc:<20}" optsec.add_text( textwrap.fill(_ln, width=TERMSIZE, subsequent_indent=opt_indent), newline=False ) if opt.long_desc: _size = TERMSIZE - 10 space = ' ' * 8 optsec.add_text( textwrap.fill(opt.long_desc, width=_size, initial_indent=space, subsequent_indent=space), newline=False ) @classmethod def display_self_help(cls, section): section.set_title("SoS Plugin Detailed Help") section.add_text( "Plugins are what define what collections occur for a given " f"{bold('sos report')} execution. Plugins are generally " "representative of a single system component (e.g. kernel), " "package (e.g. podman), or similar construct. Plugins will " "typically specify multiple files or directories to copy, as well" " as commands to execute and collect the output of for further " "analysis." ) subsec = section.add_section('Plugin Enablement') subsec.add_text( 'Plugins will be automatically enabled based on one of several ' 'triggers - a certain package being installed, a command or file ' 'existing, a kernel module being loaded, etc...' ) subsec.add_text( "Plugins may also be enabled or disabled by name using the " f"{bold('-e $name')} or {bold('-n $name')} options respectively." ) subsec.add_text( "Certain plugins may only be available for specific distributions " "or may behave differently on different distributions based on how" " the component for that plugin is installed or how it operates." f" When using {bold('sos help report.plugins.$plugin')}, help will" " be displayed for the version of the plugin appropriate for your " "distribution." ) optsec = section.add_section('Using Plugin Options') optsec.add_text( "Many plugins support additional options to enable/disable or in " "some other way modify the collections it makes. Plugin options " f"are set using the {bold('-k $plugin_name.$option_name=$value')} " "syntax. Options that are on/off toggles may exclude setting a " "value, which will be interpreted as enabling that option.\n\nSee" f" specific plugin help sections or {bold('sos report -l')} for " "more information on these options" ) seealso = section.add_section('See Also') _also = { 'report.plugins.$plugin': 'Help for a specific $plugin', 'policies': 'Information on distribution policies' } seealso.add_text( "Additional relevant information may be available in these " "help sections:\n\n%s" % "\n".join( f"{' ':>8}{sec:<30}{desc:<30}" for sec, desc in _also.items() ), newline=False ) def _format_msg(self, msg): # safeguard against non-UTF logging, see #2790 for reference return (f"[plugin:{self.name()}] " f"{msg.encode('utf-8', 'replace').decode()}") def _log_error(self, msg): self.soslog.error(self._format_msg(msg)) def _log_warn(self, msg): self.soslog.warning(self._format_msg(msg)) def _log_info(self, msg): self.soslog.info(self._format_msg(msg)) def _log_debug(self, msg): self.soslog.debug(self._format_msg(msg)) def strip_sysroot(self, path): """Remove the configured sysroot from a filesystem path :param path: The filesystem path to strip sysroot from :type path: ``str`` :returns: The stripped filesystem path :rtype: ``str`` """ if not self.use_sysroot(): return path if self.sysroot and path.startswith(self.sysroot): return path[len(self.sysroot):] return path def use_sysroot(self): """Determine if the configured sysroot needs to be used :returns: ``True`` if sysroot is not `/`, else ``False`` :rtype: ``bool`` """ return self.sysroot != os.path.abspath(os.sep) def tmp_in_sysroot(self): """Check if sysroot is within the archive's temp directory :returns: ``True`` if sysroot is in the archive's temp directory, else ``False`` :rtype: ``bool`` """ # if sysroot is still None, that implies '/' _sysroot = self.sysroot or '/' paths = [_sysroot, self.archive.get_tmp_dir()] return os.path.commonprefix(paths) == _sysroot def is_installed(self, package_name): """Is the package $package_name installed? :param package_name: The name of the package to check :type package_name: ``str`` :returns: ``True`` id the package is installed, else ``False`` :rtype: ``bool`` """ return ( len(self.policy.package_manager.all_pkgs_by_name(package_name)) > 0 ) def is_snap_installed(self, package_name): """Is the snap package $package_name installed? :param package_name: The name of the package to check :type package_name: ``str`` :returns: ``True`` if the snap package is installed, else ``False`` :rtype: ``bool`` """ pkg = self.policy.package_manager.pkg_by_name(package_name) return pkg is not None and pkg['pkg_manager'] == 'snap' def is_service(self, name): """Does the service $name exist on the system? :param name: The name of the service to check :type name: ``str`` :returns: ``True`` if service is present on the system, else ``False`` :rtype: ``bool`` """ return self.policy.init_system.is_service(name) def is_service_enabled(self, name): """Is the service $name enabled? :param name: The name of the service to check :type name: ``str`` :returns: ``True if service is enabled on the system, else ``False`` :rtype: ``bool`` """ return self.policy.init_system.is_enabled(name) def is_service_disabled(self, name): """Is the service $name disabled? :param name: The name of the service to check :type name: ``str`` :returns: ``True`` if service is disabled on the system, else ``False`` :rtype: ``bool`` """ return self.policy.init_system.is_disabled(name) def is_service_running(self, name): """Is the service $name currently running? :param name: The name of the service to check :type name: ``str`` :returns: ``True`` if the service is running on the system, else ``False`` :rtype: ``bool`` """ return self.policy.init_system.is_running(name) def get_service_status(self, name): """Return the reported status for service $name :param name: The name of the service to check :type name: ``str`` :returns: The state of the service according to the init system :rtype: ``str`` """ return self.policy.init_system.get_service_status(name)['status'] def get_service_names(self, regex): """Get all service names matching regex :param regex: A regex to match service names against :type regex: ``str`` :returns: All service name(s) matching the given `regex` :rtype: ``list`` """ return self.policy.init_system.get_service_names(regex) def set_predicate(self, pred): """Set or clear the default predicate for this plugin. :param pred: The predicate to use as the default for this plugin :type pred: ``SoSPredicate`` """ self.predicate = pred def set_cmd_predicate(self, pred): """Set or clear the default predicate for command collection for this plugin. If set, this predecate takes precedence over the `Plugin` default predicate for command and journal data collection. :param pred: The predicate to use as the default command predicate :type pred: ``SoSPredicate`` """ self.cmd_predicate = pred def get_predicate(self, cmd=False, pred=None): """Get the current default `Plugin` or command predicate. :param cmd: If a command predicate is set, should it be used. :type cmd: ``bool`` :param pred: An optional predicate to pass if no command or plugin predicate is set :type pred: ``SoSPredicate`` :returns: `pred` if neither a command predicate or plugin predicate is set. The command predicate if one is set and `cmd` is ``True``, else the plugin default predicate (which may be ``None``). :rtype: ``SoSPredicate`` or ``None`` """ if pred is not None: return pred if cmd and self.cmd_predicate is not None: return self.cmd_predicate return self.predicate def test_predicate(self, cmd=False, pred=None): """Test the current predicate and return its value. :param cmd: ``True`` if the predicate is gating a command or ``False`` otherwise. :param pred: An optional predicate to override the current ``Plugin`` or command predicate. :returns: ``True`` or ``False`` based on predicate evaluation, or ``False`` if no predicate :rtype: ``bool`` """ pred = self.get_predicate(cmd=cmd, pred=pred) if pred is not None: return bool(pred) return False def log_skipped_cmd(self, cmd, pred, changes=False): """Log that a command was skipped due to predicate evaluation. Emit a warning message indicating that a command was skipped due to predicate evaluation. If ``kmods`` or ``services`` are ``True`` then the list of expected kernel modules or services will be included in the log message. If ``allow_changes`` is ``True`` a message indicating that the missing data can be collected by using the "--allow-system-changes" command line option will be included. :param cmd: The command that was skipped :type cmd: ``str`` :param pred: The predicate that caused the command to be skipped :type pred: ``SoSPredicate`` :param changes: Is the `--allow-system-changes` enabled :type changes: ``bool`` """ if pred is None: pred = SoSPredicate(self) msg = f"skipped command '{cmd}': {pred.report_failure()}" if changes: msg += " Use '--allow-system-changes' to enable collection." self._log_warn(msg) def do_cmd_private_sub(self, cmd, desc=""): """Remove certificate and key output archived by sos report. Any matching instances are replaced with: '-----SCRUBBED' and this function does not take a regexp or substituting string. :param cmd: The name of the binary to scrub certificate output from :type cmd: ``str`` :param desc: An identifier to add to the `SCRUBBED` header line :type desc: ``str`` :returns: Number of replacements made :rtype: ``int`` """ if not self.executed_commands: return 0 self._log_debug( f"Scrubbing certs and keys for commands matching {cmd}") replace = f"{_cert_replace} {desc}" if desc else _cert_replace return self.do_cmd_output_sub(cmd, _certmatch, replace) def do_cmd_output_sub(self, cmd, regexp, subst): """Apply a regexp substitution to command output archived by sos This is used to obfuscate sensitive information captured by command output collection via plugins. :param cmd: The command name/binary name for collected output that needs to be obfuscated. Internally globbed with a leading and trailing `*` :type cmd: ``str`` :param regexp: A regex to match the contents of the command output against :type regexp: ``str`` or compile ``re`` object :param subst: The substitution string used to replace matches from `regexp` :type subst: ``str`` :returns: Number of replacements made :rtype: ``int`` """ globstr = '*' + cmd + '*' pattern = regexp.pattern if hasattr(regexp, "pattern") else regexp self._log_debug( f"substituting '{subst}' for '{pattern}' in commands matching " f"'{globstr}'") if not self.executed_commands: return 0 replacements = None try: for called in self.executed_commands: # was anything collected? if called['file'] is None: continue if called['binary'] == 'yes': self._log_warn("Cannot apply regex substitution to binary" f" output: '{called['exe']}'") continue if fnmatch.fnmatch(called['cmd'], globstr): path = os.path.join(self.commons['cmddir'], called['file']) self._log_debug(f"applying substitution to '{path}'") readable = self.archive.open_file(path) result, replacements = re.subn( regexp, subst, readable.read()) if replacements: self.archive.add_string(result, path) except Exception as e: msg = "regex substitution failed for '%s' with: '%s'" self._log_error(msg % (called['exe'], e)) replacements = None return replacements def do_file_private_sub(self, pathregex, desc=""): """Scrub certificate/key/etc information from files collected by sos. Files matching the provided pathregex are searched for content that resembles certificate, ssh keys, or similar information. Any matches are replaced with "-----SCRUBBED $desc" where `desc` is a description of the specific type of content being replaced, e.g. "-----SCRUBBED RSA PRIVATE KEY" so that support representatives can at least be informed of what type of content it was originally. :param pathregex: A string or regex of a filename to match against :type pathregex: ``str`` :param desc: A description of the replaced content :type desc: ``str`` """ self._log_debug("Scrubbing certs and keys for paths matching " f"{pathregex}") match = re.compile(pathregex).match replace = f"{_cert_replace} {desc}" if desc else _cert_replace file_list = [f for f in self.copied_files if match(f['srcpath'])] for i in file_list: path = i['dstpath'] if not path: continue self.do_file_sub(path, _certmatch, replace) def do_file_sub(self, srcpath, regexp, subst): """Apply a regexp substitution to a file archived by sos report. :param srcpath: Path in the archive where the file can be found :type srcpath: ``str`` :param regexp: A regex to match the contents of the file :type regexp: ``str`` or compiled ``re`` object :param subst: The substitution string to be used to replace matches within the file :type subst: ``str`` :returns: Number of replacements made :rtype: ``int`` """ try: path = self._get_dest_for_srcpath(srcpath) self._log_debug(f"substituting scrpath '{srcpath}'") self._log_debug(f"substituting '{subst}' for '%s' in '{path}'" % regexp.pattern if hasattr(regexp, "pattern") else regexp) if not path: return 0 replacements = self.archive.do_file_sub(path, regexp, subst) except (OSError, IOError) as e: # if trying to regexp a nonexisting file, dont log it as an # error to stdout if e.errno == errno.ENOENT: msg = "file '%s' not collected, substitution skipped" self._log_debug(msg % path) else: msg = "regex substitution failed for '%s' with: '%s'" self._log_error(msg % (path, e)) replacements = 0 return replacements def do_path_regex_sub(self, pathexp, regexp, subst): """Apply a regexp substituation to a set of files archived by sos. The set of files to be substituted is generated by matching collected file pathnames against `pathexp`. :param pathexp: A regex to match filenames within the archive :type pathexp: ``str`` or compiled ``re`` object :param regexp: A regex to match against the contents of each file :type regexp: ``str`` or compiled ``re`` object :param subst: The substituion string to be used to replace matches :type subst: ``str`` """ if not hasattr(pathexp, "match"): pathexp = re.compile(pathexp) match = pathexp.match file_list = [f for f in self.copied_files if match(f['srcpath'])] for file in file_list: self.do_file_sub(file['srcpath'], regexp, subst) def do_regex_find_all(self, regex, fname): return regex_findall(regex, fname) def _copy_symlink(self, srcpath): # the target stored in the original symlink linkdest = os.readlink(srcpath) dest = os.path.join(os.path.dirname(srcpath), linkdest) # Absolute path to the link target. If SYSROOT != '/' this path # is relative to the host root file system. absdest = os.path.normpath(dest) # adjust the target used inside the report to always be relative if os.path.isabs(linkdest): # Canonicalize the link target path to avoid additional levels # of symbolic links (that would affect the path nesting level). realdir = os.path.realpath(os.path.dirname(srcpath)) reldest = os.path.relpath(linkdest, start=realdir) # trim leading /sysroot if self.use_sysroot(): reldest = reldest[len(os.sep + os.pardir):] self._log_debug(f"made link target '{linkdest}' relative as " f"'{reldest}'") else: reldest = linkdest self._log_debug(f"copying link '{srcpath}' pointing to '{linkdest}' " f"with isdir={self.path_isdir(absdest)}") dstpath = self.strip_sysroot(srcpath) # use the relative target path in the tarball self.archive.add_link(reldest, dstpath) if self.path_isdir(absdest): self._log_debug(f"link '{linkdest}' is a directory, skipping...") return self.copied_files.append({'srcpath': srcpath, 'dstpath': dstpath, 'symlink': "yes", 'pointsto': linkdest}) # Check for indirect symlink loops by stat()ing the next step # in the link chain. try: os.stat(absdest) except OSError as e: if e.errno == 40: self._log_debug(f"link '{dstpath}' is part of a file system " "loop, skipping target...") return # copy the symlink target translating relative targets # to absolute paths to pass to _do_copy_path. self._log_debug(f"normalized link target '{linkdest}' as '{absdest}'") # skip recursive copying of symlink pointing to itself. if absdest != srcpath: # this allows for ensuring we collect the host's file when copying # a symlink from within a container that is within the set sysroot force = (absdest.startswith(self.sysroot) and self.policy._in_container) self._do_copy_path(absdest, force=force) else: self._log_debug(f"link '{linkdest}' points to itself, skipping " "target...") def _copy_dir(self, srcpath): try: for name in self.listdir(srcpath): self._log_debug(f"recursively adding '{name}' from " f"'{srcpath}'") path = os.path.join(srcpath, name) self._do_copy_path(path) except OSError as e: if e.errno == errno.EPERM or errno.EACCES: msg = "Permission denied" self._log_warn(f"_copy_dir: '{srcpath}' {msg}") return if e.errno == errno.ELOOP: msg = "Too many levels of symbolic links copying" self._log_error(f"_copy_dir: {msg} '{srcpath}'") return raise def _get_dest_for_srcpath(self, srcpath): if self.use_sysroot(): srcpath = self.path_join(srcpath) for copied in self.copied_files: if srcpath == copied["srcpath"]: return copied["dstpath"] return None def _is_forbidden_path(self, path): return any( re.match(forbid, path) for forbid in self.forbidden_paths ) def _is_policy_forbidden_path(self, path): return any( fnmatch.fnmatch(path, fp) for fp in self.policy.forbidden_paths ) def _is_skipped_path(self, path): """Check if the given path matches a user-provided specification to ignore collection of via the ``--skip-files`` option :param path: The filepath being collected :type path: ``str`` :returns: ``True`` if file should be skipped, else ``False`` """ for _skip_path in self.skip_files: if fnmatch.fnmatch(path, _skip_path): return True return False def _copy_node(self, path, st): dev_maj = os.major(st.st_rdev) dev_min = os.minor(st.st_rdev) mode = st.st_mode self.archive.add_node(path, mode, os.makedev(dev_maj, dev_min)) # Methods for copying files and shelling out def _do_copy_path(self, srcpath, dest=None, force=False): """Copy file or directory to the destination tree. If a directory, then everything below it is recursively copied. A list of copied files are saved for use later in preparing a report. """ if self._timeout_hit: return None if self._is_forbidden_path(srcpath): self._log_debug(f"skipping forbidden path '{srcpath}'") return None if not dest: dest = srcpath if self.use_sysroot(): dest = self.strip_sysroot(dest) try: st = os.lstat(srcpath) except (OSError, IOError): self._log_info(f"failed to stat '{srcpath}'") return None if stat.S_ISLNK(st.st_mode): self._copy_symlink(srcpath) return None if stat.S_ISDIR(st.st_mode) and os.access(srcpath, os.R_OK): # copy empty directory if not self.listdir(srcpath): self.archive.add_dir(dest) return None self._copy_dir(srcpath) return None # handle special nodes (block, char, fifo, socket) if not (stat.S_ISREG(st.st_mode) or stat.S_ISDIR(st.st_mode)): ntype = _node_type(st) self._log_debug(f"creating {ntype} node at archive:'{dest}'") self._copy_node(dest, st) return None # if we get here, it's definitely a regular file (not a symlink or dir) self._log_debug(f"copying path '{srcpath}' to archive:'{dest}'") # if not readable(srcpath) if not st.st_mode & 0o444: # FIXME: reflect permissions in archive self.archive.add_string("", dest) else: self.archive.add_file(srcpath, dest, force=force) self.copied_files.append({ 'srcpath': srcpath, 'dstpath': dest, 'symlink': "no" }) return None def add_forbidden_path(self, forbidden): """Specify a path, or list of paths, to not copy, even if it's part of an ``add_copy_spec()`` call :param forbidden: A filepath to forbid collection from :type forbidden: ``str`` or a ``list`` of strings """ if isinstance(forbidden, str): forbidden = [forbidden] if self.use_sysroot(): forbidden = [self.path_join(f) for f in forbidden] for forbid in forbidden: self._log_info(f"adding forbidden path '{forbid}'") if "*" in forbid: # calling translate() here on a dir-level path will break the # re.match() call during path comparison forbid = fnmatch.translate(forbid) self.forbidden_paths.append(forbid) def set_option(self, optionname, value): """Set the named option to value. Ensure the original type of the option value is preserved :param optioname: The name of the option to set :type optioname: ``str`` :param value: The value to set the option to :returns: ``True`` if the option is successfully set, else ``False`` :rtype: ``bool`` """ if optionname in self.options: try: self.options[optionname].set_value(value) return True except Exception as err: self._log_error(err) raise return False def get_option(self, optionname, default=0): """Retrieve the value of the requested option, searching in order: parameters passed from the command line, set via `set_option()`, or the global_plugin_options dict. `optionname` may be iterable, in which case this function will return the first match. :param optionname: The name of the option to retrieve the value of :type optionname: ``str`` :param default: Optionally provide a default value to return if no option matching `optionname` is found. Default 0 :returns: The value of `optionname` if found, else `default` """ global_options = ( 'all_logs', 'allow_system_changes', 'cmd_timeout', 'journal_size', 'log_size', 'plugin_timeout', 'since', 'verify' ) if optionname in global_options: return getattr(self.commons['cmdlineopts'], optionname) if optionname in self.options: opt = self.options[optionname] if not default or opt.value is not None: return opt.value return default return default def _add_copy_paths(self, copy_paths): self.copy_paths.update(copy_paths) def add_file_tags(self, tagdict): """Apply a tag to a file matching a given regex, for use when a file is copied by a more generic copyspec. :param tagdict: A dict containing the filepatterns to match and the tag(s) to apply to those files :type tagdict: ``dict`` `tagdict` takes the form `{file_pattern: tag}`, E.G. to match all bond devices from /proc/net/bonding with the tag `bond`, use `{'/proc/net/bonding/bond.*': ['bond']}` """ for fname in tagdict: if isinstance(tagdict[fname], str): tagdict[fname] = [tagdict[fname]] self.filetags.update(tagdict) def get_tags_for_file(self, fname): """Get the tags that should be associated with a file matching a given regex :param fname: A regex for filenames to be matched against :type fname: ``str`` :returns: The tag(s) associated with `fname` :rtype: ``list`` of strings """ tags = [] for key, val in self.filetags.items(): if re.match(key, fname): tags.extend(val) return tags def generate_copyspec_tags(self): """After file collections have completed, retroactively generate manifest entries to apply tags to files copied by generic copyspecs """ for file_regex, tag in self.filetags.items(): manifest_data = { 'specification': file_regex, 'files_copied': [], 'tags': tag } matched_files = [] for cfile in self.copied_files: if re.match(file_regex, cfile['srcpath']): matched_files.append(cfile['dstpath'].lstrip('/')) if matched_files: manifest_data['files_copied'] = matched_files self.manifest.files.append(manifest_data) def add_copy_spec(self, copyspecs, sizelimit=None, maxage=None, tailit=True, pred=None, tags=[], container=None): """Add a file, directory, or globs matching filepaths to the archive :param copyspecs: Files, directories, or globs matching filepaths :type copyspecs: ``str`` or a ``list`` of strings :param sizelimit: Limit the total size of collections from `copyspecs` to this size in MB :type sizelimit: ``int`` :param maxage: Collect files with `mtime` not older than this many hours :type maxage: ``int`` :param tailit: Should a file that exceeds `sizelimit` be tail'ed to fit the remaining space to meet `sizelimit` :type tailit: ``bool`` :param pred: A predicate to gate if `copyspecs` should be collected :type pred: ``SoSPredicate`` :param tags: A tag or set of tags to add to the metadata information for this collection :type tags: ``str`` or a ``list`` of strings :param container: Container(s) from which this file should be copied :type container: ``str`` or a ``list`` of strings `copyspecs` will be expanded and/or globbed as appropriate. Specifying a directory here will cause the plugin to attempt to collect the entire directory, recursively. If `container` is specified, `copyspecs` may only be explicit paths, not globs as currently container runtimes do not support glob expansion as part of the copy operation. Note that `sizelimit` is applied to each `copyspec`, not each file individually. For example, a copyspec of ``['/etc/foo', '/etc/bar.conf']`` and a `sizelimit` of 25 means that sos will collect up to 25MB worth of files within `/etc/foo`, and will collect the last 25MB of `/etc/bar.conf`. """ since = None if self.get_option('since'): since = self.get_option('since') logarchive_pattern = re.compile(r'.*((\.(zip|gz|bz2|xz))|[-.][\d]+)$') configfile_pattern = re.compile(fr"^{self.path_join('etc')}/*") if not self.test_predicate(pred=pred): self._log_info(f"skipped copy spec '{copyspecs}' due to predicate" f" ({self.get_predicate(pred=pred)})") return None if sizelimit is None: sizelimit = self.get_option("log_size") if self.get_option('all_logs'): sizelimit = None if sizelimit: sizelimit *= 1024 * 1024 # in MB if not copyspecs: return False if isinstance(copyspecs, str): copyspecs = [copyspecs] if isinstance(tags, str): tags = [tags] def get_filename_tag(fname): """Generate a tag to add for a single file copyspec This tag will be set to the filename, minus any extensions except for special extensions like .conf or .log, which will be mangled to _conf or similar. """ if fname.startswith(('/proc', '/sys')): return None _fname = fname.split('/')[-1] _fname = _fname.replace('-', '_') if _fname.endswith(('.conf', '.log', '.txt')): return _fname.replace('.', '_') return None def getmtime(path): """ Files should be sorted in most-recently-modified order, so that we collect the newest data first before reaching the limit.""" try: return os.path.getmtime(path) except OSError: return 0 def time_filter(path): """ When --since is passed, or maxage is coming from the plugin, we need to filter out older files """ # skip config files or not-logarchive files from the filter if ((logarchive_pattern.search(path) is None) or (configfile_pattern.search(path) is not None)): return True filetime = getmtime(path) filedatetime = datetime.fromtimestamp(filetime) if ((since and filedatetime < since) or (maxage and (time()-filetime < maxage*3600))): return False return True for copyspec in copyspecs: if not (copyspec and len(copyspec)): return False if not container: if self.use_sysroot(): copyspec = self.path_join(copyspec) files = self._expand_copy_spec(copyspec) if len(files) == 0: continue else: files = [copyspec] _spec_tags = [] if len(files) == 1: _spec = get_filename_tag(files[0]) if _spec: _spec_tags.append(_spec) _spec_tags.extend(self.get_tags_for_file(files[0])) _spec_tags.extend(tags) _spec_tags = list(set(_spec_tags)) if container: if isinstance(container, str): container = [container] for con in container: if not self.container_exists(con): continue _tail = False if sizelimit: # to get just the size, stat requires a literal '%s' # which conflicts with python string formatting cmd = f"stat -c %s {copyspec}" ret = self.exec_cmd(cmd, container=con) if ret['status'] == 0: try: consize = int(ret['output']) if consize > sizelimit: _tail = True except ValueError: self._log_info( f"unable to determine size of '{copyspec}'" f" in container '{con}'. Skipping " "collection." ) continue else: self._log_debug( f"stat of '{copyspec}' in container '{con}' " "failed, skipping collection: " f"{ret['output']}") continue self.container_copy_paths.append( (con, copyspec, sizelimit, _tail, _spec_tags) ) self._log_info( f"added collection of '{copyspec}' from container " f"'{con}'" ) # break out of the normal flow here as container file # copies are done via command execution, not raw cp/mv # operations continue if since or maxage: files = list(filter(time_filter, files)) files.sort(key=getmtime, reverse=True) current_size = 0 limit_reached = False _manifest_files = [] for _file in files: if _file in self.copy_paths: self._log_debug(f"skipping redundant file '{_file}'") continue if self._is_forbidden_path(_file): self._log_debug(f"skipping forbidden path '{_file}'") continue if self._is_policy_forbidden_path(_file): self._log_debug( f"skipping policy forbidden path '{_file}'") continue if self._is_skipped_path(_file): self._log_debug(f"skipping excluded path '{_file}'") continue if limit_reached: self._log_info(f"skipping '{_file}' over size limit") continue try: file_size = os.stat(_file)[stat.ST_SIZE] except OSError: # if _file is a broken symlink, we should collect it, # otherwise skip it if self.path_islink(_file): file_size = 0 else: self._log_info(f"failed to stat '{_file}', skipping") continue current_size += file_size if sizelimit and current_size > sizelimit: limit_reached = True if tailit: if file_is_binary(_file): self._log_info( f"File '{_file}' is over size limit and is " "binary. Skipping collection." ) continue self._log_info( f"File '{_file}' is over size limit, will instead " "tail the file during collection phase." ) add_size = sizelimit + file_size - current_size self._tail_files_list.append((_file, add_size)) _manifest_files.append(_file.lstrip('/')) else: # size limit not exceeded, copy the file _manifest_files.append(_file.lstrip('/')) self._add_copy_paths([_file]) # in the corner case we just reached the sizelimit, we # should collect the whole file and stop limit_reached = (sizelimit and current_size == sizelimit) if not container: # container collection manifest additions are handled later if self.manifest: self.manifest.files.append({ 'specification': copyspec, 'files_copied': _manifest_files, 'tags': _spec_tags }) return None def add_device_cmd(self, cmds, devices, timeout=None, sizelimit=None, chroot=True, runat=None, env=None, binary=False, prepend_path=None, whitelist=[], blacklist=[], tags=[], priority=10, subdir=None): """Run a command or list of commands against devices discovered during sos initialization. Any commands specified by cmd will be iterated over the list of the specified devices. Commands passed to this should include a '%(dev)s' variable for substitution. :param cmds: The command(s) to run against the list of devices :type cmds: ``str`` or a ``list`` of strings :param devices: The device paths to run `cmd` against. This should be either a list of devices/device paths or a key in the devices dict discovered by sos during initialization. :type devices: ``str`` or a ``list`` of devices or device paths. :param timeout: Timeout in seconds to allow each `cmd` to run :type timeout: ``int`` :param sizelimit: Maximum amount of output to collect, in MB :type sizelimit: ``int`` :param chroot: Should sos chroot the command(s) being run :type chroot: ``bool`` :param runat: Set the filesystem location to execute the command from :type runat: ``str`` :param env: Set environment variables for the command(s) being run :type env: ``dict`` :param binary: Is the output collected going to be binary data :type binary: ``bool`` :param prepend_path: The leading path for block device names :type prepend_path: ``str`` or ``None`` :param whitelist: Limit the devices the `cmds` will be run against to devices matching these item(s) :type whitelist: ``list`` of ``str`` :param blacklist: Do not run `cmds` against devices matching these item(s) :type blacklist: ``list`` of ``str`` :param subdir: Write the command output to this subdir within the Plugin directory :type subdir: ``str`` """ _dev_tags = [] if isinstance(tags, str): tags = [tags] if isinstance(devices, str): devices = [devices] _devs = recursive_dict_values_by_key(self.devices, devices) if whitelist: if isinstance(whitelist, str): whitelist = [whitelist] _devs = [d for d in _devs if any(re.match(f"(.*)?{wl}", d) for wl in whitelist)] if blacklist: if isinstance(blacklist, str): blacklist = [blacklist] _devs = [d for d in _devs if not any(re.match(f"(.*)?{bl}", d) for bl in blacklist)] _dev_tags.extend(tags) self._add_device_cmd(cmds, _devs, timeout=timeout, sizelimit=sizelimit, chroot=chroot, runat=runat, env=env, binary=binary, prepend_path=prepend_path, tags=_dev_tags, priority=priority, subdir=subdir) def _add_device_cmd(self, cmds, devices, timeout=None, sizelimit=None, chroot=True, runat=None, env=None, binary=False, prepend_path=None, tags=[], priority=10, subdir=None): """Run a command against all specified devices on the system. """ if isinstance(cmds, str): cmds = [cmds] if isinstance(devices, str): devices = [devices] sizelimit = sizelimit or self.get_option('log_size') for cmd in cmds: for device in devices: _dev_tags = [device] _dev_tags.extend(tags) if prepend_path: device = self.path_join(prepend_path, device) _cmd = cmd % {'dev': device} self._add_cmd_output(cmd=_cmd, timeout=timeout, sizelimit=sizelimit, chroot=chroot, runat=runat, env=env, binary=binary, tags=_dev_tags, priority=priority, subdir=subdir) def _add_cmd_output(self, **kwargs): # pylint: disable=no-member """Internal helper to add a single command to the collection list.""" pred = kwargs.pop('pred') if 'pred' in kwargs else SoSPredicate(self) if 'priority' not in kwargs: kwargs['priority'] = 10 if 'changes' not in kwargs: kwargs['changes'] = False if (not getattr(SoSCommand(**kwargs), "snap_cmd", False) and (self.get_option('all_logs') or kwargs['sizelimit'] == 0)): kwargs['to_file'] = True if "snap_cmd" in kwargs: kwargs.pop("snap_cmd") soscmd = SoSCommand(**kwargs) self._log_debug(f"packed command: {str(soscmd)}") for _skip_cmd in self.skip_commands: # This probably seems weird to be doing filename matching on the # commands, however we want to remain consistent with our regex # matching with file paths, which sysadmins are almost guaranteed # to assume will use shell-style unix matching if fnmatch.fnmatch(soscmd.cmd, _skip_cmd): self._log_debug(f"skipping excluded command '{soscmd.cmd}'") return if self.test_predicate(cmd=True, pred=pred): self.collect_cmds.append(soscmd) user = "" if getattr(soscmd, "runas", None) is not None: user = f", as the {soscmd.runas} user" self._log_info(f"added cmd output '{soscmd.cmd}'{user}") else: self.log_skipped_cmd(soscmd.cmd, pred, changes=soscmd.changes) def add_dir_listing(self, paths, tree=False, recursive=False, chroot=True, env=None, sizelimit=None, pred=None, subdir=None, tags=[], runas=None, container=None, suggest_filename=None): """ Used as a way to standardize our collections of directory listings, either as an output of `ls` or `tree` depending on if the `tree` parameter is set to `True`. This is ultimately a wrapper around `add_cmd_output()` and supports several, but not all, of the options for that method. :param paths: The path(s) to collect a listing for :type paths: ``str`` or a ``list`` of ``str``s :param tree: Collect output with `tree` instead of `ls` :type tree: ``bool`` (default: False) :param recursive: Recursively list directory contents with `ls` :type recursive: ``bool`` (default: False) """ if isinstance(paths, str): paths = [paths] paths = [p for p in paths if self.path_exists(p)] if not tree: options = f"alhZ{'R' if recursive else ''}" else: options = 'lhp' for path in paths: self.add_cmd_output( f"{'tree' if tree else 'ls'} -{options} {path}", chroot=chroot, env=env, sizelimit=sizelimit, pred=pred, subdir=subdir, tags=tags, container=container, runas=runas, suggest_filename=suggest_filename ) def add_cmd_output(self, cmds, suggest_filename=None, root_symlink=None, timeout=None, stderr=True, chroot=True, runat=None, env=None, binary=False, sizelimit=None, pred=None, subdir=None, changes=False, foreground=False, tags=[], priority=10, cmd_as_tag=False, container=None, to_file=False, runas=None, snap_cmd=False): """Run a program or a list of programs and collect the output Output will be limited to `sizelimit`, collecting the last X amount of command output matching `sizelimit`. Unless `suggest_filename` is set, the file that the output is saved to will match the command as it was executed, and will be saved under `sos_commands/$plugin` :param cmds: The command(s) to execute :type cmds: ``str`` or a ``list`` of strings :param suggest_filename: Override the name of the file output is saved to within the archive :type suggest_filename: ``str`` :param root_symlink: If set, create a symlink with this name in the archive root :type root_symlink: ``str`` :param timeout: Timeout in seconds to allow each `cmd` to run for :type timeout: ``int`` :param stderr: Should stderr output be collected :type stderr: ``bool`` :param chroot: Should sos chroot the `cmds` being run :type chroot: ``bool`` :param runat: Run the `cmds` from this location in the filesystem :type runat: ``str`` :param env: Set environment variables for the `cmds` being run :type env: ``dict`` :param binary: Is the command expected to produce binary output :type binary: ``bool`` :param sizelimit: Maximum amount of output in MB to save :type sizelimit: ``int`` :param pred: A predicate to gate if `cmds` should be collected or not :type pred: ``SoSPredicate`` :param subdir: Save output to this subdirectory, within the plugin's directory under sos_commands :type subdir: ``str`` :param changes: Do `cmds` have the potential to change system state :type changes: ``int`` :param foreground: Should the `cmds` be run in the foreground, with an attached TTY :type foreground: ``bool`` :param tags: A tag or set of tags to add to the metadata entries for the `cmds` being run :type tags: ``str`` or a ``list`` of strings :param priority: The priority with which this command should be run, lower values will run before higher values :type priority: ``int`` :param cmd_as_tag: Should the command string be automatically formatted to a tag? :type cmd_as_tag: ``bool`` :param container: Run the specified `cmds` inside a container with this ID or name :type container: ``str`` :param to_file: Should command output be written directly to a new file rather than stored in memory? :type to_file: ``bool`` :param runas: Run the `cmd` as the `runas` user :type runas: ``str`` :param snap_cmd: Are the commands being run from a snap? :type snap_cmd: ``bool`` """ if isinstance(cmds, str): cmds = [cmds] if len(cmds) > 1 and (suggest_filename or root_symlink): self._log_warn("ambiguous filename or symlink for command list") if sizelimit is None: sizelimit = self.get_option("log_size") if pred is None: pred = self.get_predicate(cmd=True) for cmd in cmds: container_cmd = None if container: ocmd = cmd container_cmd = (ocmd, container) cmd = self.fmt_container_cmd(container, cmd) if not cmd: self._log_debug(f"Skipping command '{ocmd}' as the " f"requested container '{container}' does " "not exist.") continue self._add_cmd_output(cmd=cmd, suggest_filename=suggest_filename, root_symlink=root_symlink, timeout=timeout, stderr=stderr, chroot=chroot, runat=runat, env=env, binary=binary, sizelimit=sizelimit, pred=pred, subdir=subdir, tags=tags, changes=changes, foreground=foreground, priority=priority, cmd_as_tag=cmd_as_tag, to_file=to_file, container_cmd=container_cmd, runas=runas, snap_cmd=snap_cmd) def add_cmd_tags(self, tagdict): """Retroactively add tags to any commands that have been run by this plugin that match a given regex :param tagdict: A dict containing the command regex and associated tags :type tagdict: ``dict`` `tagdict` takes the form of {cmd_regex: tags}, for example to tag all commands starting with `foo` with the tag `bar`, use {'foo.*': ['bar']} """ for cmd in tagdict: if isinstance(tagdict[cmd], str): tagdict[cmd] = [tagdict[cmd]] self.cmdtags.update(tagdict) def get_tags_for_cmd(self, cmd): """Get the tag(s) that should be associated with the given command :param cmd: The command that tags should be applied to :type cmd: ``str`` :returns: Any tags associated with the command :rtype: ``list`` """ for key, val in self.cmdtags.items(): if re.match(key, cmd): return val return [] def get_cmd_output_path(self, name=None, make=True): """Get the path where this plugin will save command output :param name: Optionally specify a filename to use as part of the command output path :type name: ``str`` or ``None`` :param make: Attempt to create the command output path :type make: ``bool`` :returns: The path where the plugin will write command output data within the archive :rtype: ``str`` """ cmd_output_path = os.path.join(self.archive.get_tmp_dir(), 'sos_commands', self.name()) if name: cmd_output_path = os.path.join(cmd_output_path, name) if make: os.makedirs(cmd_output_path, exist_ok=True) return cmd_output_path def file_grep(self, regexp, *fnames): """Grep through file(s) for a specific string or regex :param regexp: The string or regex to search for :type regexp: ``str`` :param fnames: Paths to grep through :type fnames: ``str``, ``list`` of string, or open file objects :returns: Lines matching `regexp` :rtype: ``str`` """ return grep(regexp, *fnames) def _mangle_command(self, exe): name_max = self.archive.name_max() return _mangle_command(exe, name_max) def _make_command_filename(self, exe, subdir=None): """The internal function to build up a filename based on a command.""" plugin_dir = self.name() if subdir: plugin_dir += f"/{subdir}" outdir = os.path.join(self.commons['cmddir'], plugin_dir) outfn = self._mangle_command(exe) # check for collisions if os.path.exists(os.path.join(self.archive.get_tmp_dir(), outdir, outfn)): inc = 1 name_max = self.archive.name_max() while True: suffix = f".{inc}" newfn = outfn if name_max < len(newfn)+len(suffix): newfn = newfn[:(name_max-len(newfn)-len(suffix))] newfn = newfn + suffix if not os.path.exists(os.path.join(self.archive.get_tmp_dir(), outdir, newfn)): outfn = newfn break inc += 1 return os.path.join(outdir, outfn) def add_env_var(self, name): """Add an environment variable to the list of to-be-collected env vars. Collected environment variables will be saved to an `environment` file in the archive root, and any variable specified for collection will be collected in lowercase, uppercase, and the form provided :param name: The name of the environment variable to collect :type name: ``str`` """ if not isinstance(name, list): name = [name] for env in name: # get both upper and lower cased vars since a common support issue # is setting the env vars to the wrong case, and if the plugin # adds a mixed case variable name, still get that as well self._env_vars.update([env, env.upper(), env.lower()]) def add_string_as_file(self, content, filename, pred=None, plug_dir=False, tags=[]): """Add a string to the archive as a file :param content: The string to write to the archive :type content: ``str`` :param filename: The name of the file to write `content` to :type filename: ``str`` :param pred: A predicate to gate if the string should be added to the archive or not :type pred: ``SoSPredicate`` :param plug_dir: Should the string be saved under the plugin's dir in sos_commands/? If false, save to sos_strings/ :type plug_dir: ``bool`` :param tags: A tag or set of tags to add to the manifest entry for this collection :type tags: ``str`` or a ``list`` of strings """ if not self.test_predicate(cmd=False, pred=pred): self._log_info("skipped string due to predicate " f"({self.get_predicate(pred=pred)})") return sos_dir = 'sos_commands' if plug_dir else 'sos_strings' filename = os.path.join(sos_dir, self.name(), filename) if isinstance(tags, str): tags = [tags] self.copy_strings.append((content, filename, tags)) self._log_debug(f"added string as '{filename}'") def _collect_cmd_output(self, cmd, suggest_filename=None, root_symlink=False, timeout=None, stderr=True, chroot=True, runat=None, env=None, binary=False, sizelimit=None, subdir=None, changes=False, foreground=False, tags=[], priority=10, cmd_as_tag=False, to_file=False, container_cmd=False, runas=None): """Execute a command and save the output to a file for inclusion in the report. Positional Arguments: :param cmd: The command to run Keyword Arguments: :param suggest_filename: Filename to use when writing to the archive :param root_symlink: Create a symlink in the archive root :param timeout: Time in seconds to allow a cmd to run :param stderr: Write stderr to stdout? :param chroot: Perform chroot before running cmd? :param runat: Run the command from this location, overriding chroot :param env: Dict of env vars to set for the cmd :param binary: Is the output in binary? :param sizelimit: Maximum size in MB of output to save :param subdir: Subdir in plugin directory to save to :param changes: Does this cmd potentially make a change on the system? :param foreground: Run the `cmd` in the foreground with a TTY :param tags: Add tags in the archive manifest :param cmd_as_tag: Format command string to tag :param to_file: Write output directly to file instead of saving in memory :param runas: Run the `cmd` as the `runas` user :returns: dict containing status, output, and filename in the archive for the executed cmd """ if self._timeout_hit: return None if timeout is None: timeout = self.cmdtimeout _tags = [] if isinstance(tags, str): tags = [tags] _tags.extend(tags) _tags.extend(self.get_tags_for_cmd(cmd)) if cmd_as_tag: _tags.append(re.sub(r"[^\w\.]+", "_", cmd)) _tags = list(set(_tags)) _env = self._get_cmd_environment(env) if chroot or self.commons['cmdlineopts'].chroot == 'always': root = self.sysroot else: root = None if suggest_filename: outfn = self._make_command_filename(suggest_filename, subdir) else: outfn = self._make_command_filename(cmd, subdir) outfn_strip = outfn[len(self.commons['cmddir'])+1:] if to_file: self._log_debug(f"collecting '{cmd}' output directly to disk") self.archive.check_path(outfn, P_FILE) out_file = os.path.join(self.archive.get_archive_path(), outfn) else: out_file = False start = time() result = sos_get_command_output( cmd, timeout=timeout, stderr=stderr, chroot=root, chdir=runat, env=_env, binary=binary, sizelimit=sizelimit, poller=self.check_timeout, foreground=foreground, to_file=out_file, runas=runas ) end = time() run_time = end - start if result['status'] == 124: warn = f"command '{cmd}' timed out after {timeout}s" self._log_warn(warn) if to_file: msg = (" - output up until the timeout may be available at " f"{outfn}") self._log_debug(f"{warn}{msg}") manifest_cmd = { 'command': cmd.split(' ')[0], 'parameters': cmd.split(' ')[1:], 'exec': cmd, 'filepath': outfn if to_file else None, 'truncated': result['truncated'], 'return_code': result['status'], 'priority': priority, 'start_time': start, 'end_time': end, 'run_time': run_time, 'tags': _tags } # command not found or not runnable if result['status'] == 126 or result['status'] == 127: # automatically retry chroot'ed commands in the host namespace if root and root != '/': if self.commons['cmdlineopts'].chroot != 'always': self._log_info(f"command '{cmd.split()[0]}' not found in " f"{root} - re-trying in host root") result = sos_get_command_output( cmd, timeout=timeout, chroot=False, chdir=runat, env=env, binary=binary, sizelimit=sizelimit, poller=self.check_timeout, to_file=out_file ) run_time = time() - start self._log_debug(f"could not run '{cmd}': command not found") # Exit here if the command was not found in the chroot check above # as otherwise we will create a blank file in the archive if result['status'] in [126, 127]: if self.manifest: self.manifest.commands.append(manifest_cmd) return result self._log_debug(f"collected output of '{cmd.split()[0]}' in {run_time}" f" (changes={changes})") if result['truncated']: self._log_info(f"collected output of '{cmd.split()[0]}' was " "truncated") linkfn = outfn outfn = outfn.replace('sos_commands', 'sos_strings') + '.tailed' if not to_file: if binary: self.archive.add_binary(result['output'], outfn) else: self.archive.add_string(result['output'], outfn) if result['truncated']: # we need to manually build the relative path from the paths that # exist within the build dir to properly drop these symlinks _outfn_path = os.path.join(self.archive.get_archive_path(), outfn) _link_path = os.path.join(self.archive.get_archive_path(), linkfn) rpath = os.path.relpath(_outfn_path, _link_path) rpath = rpath.replace('../', '', 1) self.archive.add_link(rpath, linkfn) if root_symlink: self.archive.add_link(outfn, root_symlink) # save info for later self.executed_commands.append({'cmd': cmd, 'file': outfn_strip, 'binary': 'yes' if binary else 'no'}) result['filename'] = ( os.path.join(self.archive.get_archive_path(), outfn) if outfn else '' ) if self.manifest: manifest_cmd['filepath'] = outfn manifest_cmd['run_time'] = run_time self.manifest.commands.append(manifest_cmd) if container_cmd: self._add_container_cmd_to_manifest(manifest_cmd.copy(), container_cmd) return result def collect_cmd_output(self, cmd, suggest_filename=None, root_symlink=False, timeout=None, stderr=True, chroot=True, runat=None, env=None, binary=False, sizelimit=None, pred=None, changes=False, foreground=False, subdir=None, tags=[], runas=None): """Execute a command and save the output to a file for inclusion in the report, then return the results for further use by the plugin :param cmd: The command to run :type cmd: ``str`` :param suggest_filename: Filename to use when writing to the archive :param suggest_filename: ``str`` :param root_symlink: Create a symlink in the archive root :type root_symlink: ``bool`` :param timeout: Time in seconds to allow a cmd to run :type timeout: ``int`` :param stderr: Write stderr to stdout? :type stderr: ``bool`` :param chroot: Perform chroot before running cmd? :type chroot: ``bool`` :param runat: Run the command from this location, overriding chroot :type runat: ``str`` :param env: Environment vars to set for the cmd :type env: ``dict`` :param binary: Is the output in binary? :type binary: ``bool`` :param sizelimit: Maximum size in MB of output to save :type sizelimit: ``int`` :param subdir: Subdir in plugin directory to save to :type subdir: ``str`` :param changes: Does this cmd potentially make a change on the system? :type changes: ``bool`` :param foreground: Run the `cmd` in the foreground with a TTY :type foreground: ``bool`` :param tags: Add tags in the archive manifest :type tags: ``str`` or a ``list`` of strings :param runas: Run the `cmd` as the `runas` user :type runas: ``str`` :returns: `cmd` exit status, output, and the filepath within the archive output was saved to :rtype: ``dict`` """ if not self.test_predicate(cmd=True, pred=pred): self.log_skipped_cmd(cmd, pred, changes=changes) return { 'status': None, # don't match on if result['status'] checks 'output': '', 'filename': '' } return self._collect_cmd_output( cmd, suggest_filename=suggest_filename, root_symlink=root_symlink, timeout=timeout, stderr=stderr, chroot=chroot, runat=runat, env=env, binary=binary, sizelimit=sizelimit, foreground=foreground, subdir=subdir, tags=tags, runas=runas ) def exec_cmd(self, cmd, timeout=None, stderr=True, chroot=True, runat=None, env=None, binary=False, pred=None, foreground=False, container=False, quotecmd=False, runas=None): """Execute a command right now and return the output and status, but do not save the output within the archive. Use this method in a plugin's setup() if command output is needed to build subsequent commands added to a report via add_cmd_output(). :param cmd: The command to run :type cmd: ``str`` :param timeout: Time in seconds to allow a cmd to run :type timeout: ``int`` :param stderr: Write stderr to stdout? :type stderr: ``bool`` :param chroot: Perform chroot before running cmd? :type chroot: ``bool`` :param runat: Run the command from this location, overriding chroot :type runat: ``str`` :param env: Environment vars to set for the cmd :type env: ``dict`` :param binary: Is the output in binary? :type binary: ``bool`` :param pred: A predicate to gate execution of the `cmd` :type pred: ``SoSPredicate`` :param foreground: Run the `cmd` in the foreground with a TTY :type foreground: ``bool`` :param container: Execute this command in a container with this name :type container: ``str`` :param quotecmd: Whether the cmd should be quoted. :type quotecmd: ``bool`` :param runas: Run the `cmd` as the `runas` user :type runas: ``str`` :returns: Command exit status and output :rtype: ``dict`` """ _default = {'status': None, 'output': ''} if not self.test_predicate(cmd=True, pred=pred): return _default if timeout is None: timeout = self.cmdtimeout if chroot or self.commons['cmdlineopts'].chroot == 'always': root = self.sysroot else: root = None _env = self._get_cmd_environment(env) if container: if self._get_container_runtime() is None: self._log_info(f"Cannot run cmd '{cmd}' in container " f"{container}: no runtime detected on host.") return _default if self.container_exists(container): cmd = self.fmt_container_cmd(container, cmd, quotecmd) else: self._log_info(f"Cannot run cmd '{cmd}' in container " f"{container}: no such container is running.") return sos_get_command_output(cmd, timeout=timeout, chroot=root, chdir=runat, binary=binary, env=_env, foreground=foreground, stderr=stderr, runas=runas) def _add_container_file_to_manifest(self, container, path, arcpath, tags): """Adds a file collection to the manifest for a particular container and file path. :param container: The name of the container :type container: ``str`` :param path: The filename from the container filesystem :type path: ``str`` :param arcpath: Where in the archive the file is written to :type arcpath: ``str`` :param tags: Metadata tags for this collection :type tags: ``str`` or ``list`` of strings """ if container not in self.manifest.containers: self.manifest.containers[container] = {'files': [], 'commands': []} self.manifest.containers[container]['files'].append({ 'specification': path, 'files_copied': arcpath, 'tags': tags }) def _add_container_cmd_to_manifest(self, manifest, contup): """Adds a command collection to the manifest for a particular container and creates a symlink to that collection from the relevant sos_containers/ location :param manifest: The manifest entry for the command :type manifest: ``dict`` :param contup: A tuple of (original_cmd, container_name) :type contup: ``tuple`` """ cmd, container = contup if container not in self.manifest.containers: self.manifest.containers[container] = {'files': [], 'commands': []} manifest['exec'] = cmd manifest['command'] = cmd.split(' ')[0] manifest['parameters'] = cmd.split(' ')[1:] _cdir = f"sos_containers/{container}/sos_commands/{self.name()}" _outloc = f"../../../../{manifest['filepath']}" cmdfn = self._mangle_command(cmd) conlnk = f"{_cdir}/{cmdfn}" # If check_path return None, it means that the sym link already exits, # so to avoid Error 17, trying to recreate, we will skip creation and # trust on the existing sym link (e.g. duplicate command) if self.archive.check_path(conlnk, P_LINK): os.symlink(_outloc, self.archive.dest_path(conlnk)) manifest['filepath'] = conlnk self.manifest.containers[container]['commands'].append(manifest) def _get_container_runtime(self, runtime=None): """Based on policy and request by the plugin, return a usable ContainerRuntime if one exists """ if runtime is None: if 'default' in self.policy.runtimes.keys(): return self.policy.runtimes['default'] else: for pol_runtime in list(self.policy.runtimes.keys()): if runtime == pol_runtime: return self.policy.runtimes[pol_runtime] return None def container_exists(self, name): """If a container runtime is present, check to see if a container with a given name is currently running :param name: The name or ID of the container to check presence of :type name: ``str`` :returns: ``True`` if `name` exists, else ``False`` :rtype: ``bool`` """ _runtime = self._get_container_runtime() if _runtime is not None: return (_runtime.container_exists(name) or _runtime.get_container_by_name(name) is not None) return False def get_all_containers_by_regex(self, regex, get_all=False): """Get a list of all container names and ID matching a regex :param regex: The regular expression to match :type regex: ``str`` :param get_all: Return all containers found, even terminated ones :type get_all: ``bool`` :returns: All container IDs and names matching ``regex`` :rtype: ``list`` of ``tuples`` as (id, name) """ _runtime = self._get_container_runtime() if _runtime is not None: _containers = _runtime.get_containers(get_all=get_all) return [c for c in _containers if re.match(regex, c[1])] return [] def get_container_by_name(self, name): """Get the container ID for a specific container :param name: The name of the container :type name: ``str`` :returns: The ID of the container if it exists :rtype: ``str`` or ``None`` """ _runtime = self._get_container_runtime() if _runtime is not None: return _runtime.get_container_by_name(name) return None def get_containers(self, runtime=None, get_all=False): """Return a list of all container IDs from the ``Policy`` ``ContainerRuntime`` If `runtime` is not provided, use the ``Policy`` default :param runtime: The container runtime to use, if not the default runtime detected and loaded by the ``Policy`` :type runtime: ``str`` :param get_all: Return all containers known to the `runtime`, even those that have terminated :type get_all: ``bool`` :returns: All container IDs found by the ``ContainerRuntime`` :rtype: ``list`` """ _runtime = self._get_container_runtime(runtime=runtime) if _runtime is not None: if get_all: return _runtime.get_containers(get_all=True) return _runtime.containers return [] def get_container_images(self, runtime=None): """Return a list of all image names from the Policy's ContainerRuntime If `runtime` is not provided, use the Policy default. If the specified `runtime` is not loaded, return empty. :param runtime: The container runtime to use, if not using the default runtime detected by the ``Policy`` :type runtime: ``str`` :returns: A list of container images known to the `runtime` :rtype: ``list`` """ _runtime = self._get_container_runtime(runtime=runtime) if _runtime is not None: return _runtime.images return [] def get_container_volumes(self, runtime=None): """Return a list of all volume names from the Policy's ContainerRuntime If `runtime` is not provided, use the Policy default. If the specified `runtime` is not loaded, return empty. :param runtime: The container runtime to use, if not using the default runtime detected by the ``Policy`` :type runtime: ``str`` :returns: A list of container volumes known to the `runtime` :rtype: ``list`` """ _runtime = self._get_container_runtime(runtime=runtime) if _runtime is not None: return _runtime.volumes return [] def add_container_logs(self, containers, get_all=False, **kwargs): """Helper to get the ``logs`` output for a given container or list of container names and/or regexes. Supports passthru of add_cmd_output() options :param containers: The name of the container to retrieve logs from, may be a single name or a regex :type containers: ``str`` or ``list`` of strs :param get_all: Should non-running containers also be queried? Default: False :type get_all: ``bool`` :param kwargs: Any kwargs supported by ``add_cmd_output()`` are supported here """ _runtime = self._get_container_runtime() if _runtime is not None: if isinstance(containers, str): containers = [containers] for container in containers: _cons = self.get_all_containers_by_regex(container, get_all) for _con in _cons: cmd = _runtime.get_logs_command(_con[1]) self.add_cmd_output(cmd, **kwargs) def fmt_container_cmd(self, container, cmd, quotecmd=False): """Format a command to be executed by the loaded ``ContainerRuntime`` in a specified container :param container: The name of the container to execute the `cmd` in :type container: ``str`` :param cmd: The command to run within the container :type cmd: ``str`` :param quotecmd: Whether the cmd should be quoted. :type quotecmd: ``bool`` :returns: The command to execute so that the specified `cmd` will run within the `container` and not on the host :rtype: ``str`` """ if self.container_exists(container): _runtime = self._get_container_runtime() return _runtime.fmt_container_cmd(container, cmd, quotecmd) return '' def is_module_loaded(self, module_name): """Determine whether specified module is loaded or not :param module_name: Name of kernel module to check for presence :type module_name: ``str`` :returns: ``True`` if the module is loaded, else ``False`` :rtype: ``bool`` """ return module_name in self.policy.kernel_mods # For adding output def add_alert(self, alertstring): """Add an alert to the collection of alerts for this plugin. These will be displayed in the report :param alertstring: The text to add as an alert :type alertstring: ``str`` """ self.alerts.append(alertstring) def add_custom_text(self, text): """Append text to the custom text that is included in the report. This is freeform and can include html. :param text: The text to include in the report :type text: ``str`` """ self.custom_text += text def add_service_status(self, services, **kwargs): """Collect service status information based on the ``InitSystem`` used :param services: Service name(s) to collect statuses for :type services: ``str`` or a ``list`` of strings :param kwargs: Optional arguments to pass to add_cmd_output (timeout, predicate, suggest_filename,..) """ if isinstance(services, str): services = [services] query = self.policy.init_system.query_cmd if not query: # No policy defined InitSystem, cannot use add_service_status self._log_debug('Cannot add service output, policy does not define' ' an InitSystem to use') return for service in services: self.add_cmd_output(f"{query} {service}", **kwargs) def add_journal(self, units=None, boot=None, since=None, until=None, lines=None, allfields=False, output=None, timeout=None, identifier=None, catalog=None, sizelimit=None, pred=None, tags=None, priority=10): """Collect journald logs from one of more units. :param units: Which journald units to collect :type units: ``str`` or a ``list`` of strings :param boot: A boot index using the journalctl syntax. The special values 'this' and 'last' are also accepted. :type boot: ``str`` :param since: Start time for journal messages :type since: ``str`` :param until: End time forjournal messages :type until: ``str`` :param lines: The maximum number of lines to be collected :type lines: ``int`` :param allfields: Include all journal fields regardless of size or non-printable characters :type allfields: ``bool`` :param output: Journalctl output control string, for example "verbose" :type output: ``str`` :param timeout: An optional timeout in seconds :type timeout: ``int`` :param identifier: An optional message identifier :type identifier: ``str`` :param catalog: Augment lines with descriptions from the system catalog :type catalog: ``bool`` :param sizelimit: Limit to the size of output returned in MB. Defaults to the value of --log-size. :type sizelimit: ``int`` """ journal_cmd = "journalctl --no-pager " unit_opt = " --unit %s" boot_opt = " --boot %s" since_opt = " --since '%s'" until_opt = " --until %s" lines_opt = " --lines %s" output_opt = " --output %s" identifier_opt = " --identifier %s" catalog_opt = " --catalog" if sizelimit == 0 or self.get_option("all_logs"): # allow for specific sizelimit overrides in plugins log_size = 0 else: log_size = sizelimit or self.get_option('journal_size') if isinstance(units, str): units = [units] if isinstance(tags, str): tags = [tags] elif not tags: tags = [] if units: for unit in units: journal_cmd += unit_opt % unit tags.append(f"journal_{unit}") if identifier: journal_cmd += identifier_opt % identifier if catalog: journal_cmd += catalog_opt if allfields: journal_cmd += " --all" if boot: if boot == "this": boot = "" if boot == "last": boot = "-1" journal_cmd += boot_opt % boot since = since or self.get_option('since') if since: journal_cmd += since_opt % since if until: journal_cmd += until_opt % until if lines: journal_cmd += lines_opt % lines if output: journal_cmd += output_opt % output self._log_debug(f"collecting journal: {journal_cmd}") self._add_cmd_output(cmd=journal_cmd, timeout=timeout, sizelimit=log_size, pred=pred, tags=tags, priority=priority) def _expand_copy_spec(self, copyspec): def __expand(paths): found_paths = [] paths = glob.glob(paths) for path in paths: try: # avoid recursive symlink dirs if self.path_isfile(path) or self.path_islink(path): found_paths.append(path) elif self.path_isdir(path) and self.listdir(path): found_paths.extend(__expand(self.path_join(path, '*'))) else: found_paths.append(path) except PermissionError: # when running in LXD, we've seen os.access return True for # some /sys or /proc paths yet still get a PermissionError # when calling os.listdir(), so rather than rely on that, # just catch and ignore permissions errors resulting from # security modules like apparmor/selinux # Ref: https://github.com/lxc/lxd/issues/5688 pass return list(set(found_paths)) if (os.access(copyspec, os.R_OK) and self.path_isdir(copyspec) and self.listdir(copyspec)): # the directory exists and is non-empty, recurse through it copyspec = self.path_join(copyspec, '*') expanded = glob.glob(copyspec, recursive=True) recursed_files = [] for _path in expanded: try: if self.path_isdir(_path) and self.listdir(_path): # remove the top level dir to avoid duplicate attempts to # copy the dir and its contents expanded.remove(_path) recursed_files.extend(__expand(os.path.join(_path, '*'))) except PermissionError: # same as the above in __expand(), but this time remove the # path so we don't hit another PermissionError during the # actual copy expanded.remove(_path) expanded.extend(recursed_files) return list(set(expanded)) def _collect_copy_specs(self): for path in sorted(self.copy_paths, reverse=True): self._log_info(f"collecting path '{path}'") self._do_copy_path(path) self.generate_copyspec_tags() def _collect_container_copy_specs(self): """Copy any requested files from containers here. This is done separately from normal file collection as this requires the use of a container runtime. This method will iterate over self.container_copy_paths which is a set of 5-tuples as (container, path, sizelimit, stdout, tags). """ if not self.container_copy_paths: return rt = self._get_container_runtime() if not rt: self._log_info("Cannot collect container based files - no runtime " "is present/active.") return if not rt.check_can_copy(): self._log_info("Loaded runtime '%s' does not support copying " "files from containers. Skipping collections.") return for contup in self.container_copy_paths: con, path, sizelimit, tailit, tags = contup self._log_info(f"collecting '{path}' from container '{con}'") arcdest = f"sos_containers/{con}/{path.lstrip('/')}" self.archive.check_path(arcdest, P_FILE) dest = self.archive.dest_path(arcdest) cpcmd = rt.get_copy_command( con, path, dest, sizelimit=sizelimit if tailit else None ) cpret = self.exec_cmd(cpcmd, timeout=10) if cpret['status'] == 0: if tailit: # current runtimes convert files sent to stdout to tar # archives, with no way to control that self.archive.add_string(cpret['output'], arcdest) self._add_container_file_to_manifest(con, path, arcdest, tags) else: self._log_info(f"error copying '{path}' from container " f"'{con}': {cpret['output']}") def _collect_cmds(self): self.collect_cmds.sort(key=lambda x: x.priority) for soscmd in self.collect_cmds: self._log_debug(f"unpacked command: {str(soscmd)}") user = "" if getattr(soscmd, "runas", None) is not None: user = f", as the {soscmd.runas} user" self._log_info(f"collecting output of '{soscmd.cmd}'{user}") self._collect_cmd_output(**soscmd.__dict__) def _collect_tailed_files(self): for _file, _size in self._tail_files_list: self._log_info(f"collecting tail of '{_file}' due to size limit") file_name = _file if file_name[0] == os.sep: file_name = file_name.lstrip(os.sep) strfile = ( file_name.replace(os.path.sep, ".") + ".tailed" ) self.add_string_as_file(tail(_file, _size), strfile) rel_path = os.path.relpath('/', os.path.dirname(_file)) link_path = os.path.join(rel_path, 'sos_strings', self.name(), strfile) self.archive.add_link(link_path, _file) def _collect_strings(self): for string, file_name, tags in self.copy_strings: if self._timeout_hit: return self._log_info(f"collecting string as '{file_name}'") try: self.archive.add_string(string, file_name) _name = file_name.split('/')[-1].replace('.', '_') self.manifest.strings[_name] = { 'path': file_name, 'tags': tags } except Exception as e: self._log_debug(f"could not add string '{file_name}': {e}") def _collect_manual(self): """Kick off manual collections performed by the plugin. These manual collections are anything the plugin collects outside of existing files and/or command output. Anything the plugin manually compiles or constructs for data that is included in the final archive. Plugins will need to define these collections by overriding the ``collect()`` method, similar to how plugins define their own ``setup()`` methods. """ try: self.collect() except Exception as err: self._log_error(f"Error during plugin collections: {err}") def collect(self): """If a plugin needs to manually compile some data for a collection, that should be specified here by overriding this method. These collections are run last during a plugin's execution, and as such are more likely to be interrupted by timeouts than file or command output collections. """ pass @contextlib.contextmanager def collection_file(self, fname, subdir=None, tags=[]): """Handles creating and managing files within a plugin's subdirectory within the archive, and is intended to be used to save manually compiled data generated during a plugin's ``_collect_manual()`` step of the collection phase. Plugins should call this method using a ``with`` context manager. :param fname: The name of the file within the plugin directory :type fname: ``str`` :param subdir: If needed, specify a subdir to write the file to :type subdir: ``str`` :param tags: Tags to be added to this file in the manifest :type tags: ``str`` or ``list`` of ``str`` """ try: start = time() _pfname = self._make_command_filename(fname, subdir=subdir) self.archive.check_path(_pfname, P_FILE) _name = self.archive.dest_path(_pfname) with open(_name, 'w', encoding='utf-8') as _file: self._log_debug(f"manual collection file opened: {_name}") yield _file end = time() run = end - start self._log_info(f"manual collection '{fname}' finished in {run}") if isinstance(tags, str): tags = [tags] self.manifest.collections.append({ 'name': fname, 'filepath': _pfname, 'tags': tags }) except Exception as err: self._log_info(f"Error with collection file '{fname}': {err}") def collect_plugin(self): """Collect the data for a plugin.""" start = time() self._collect_copy_specs() self._collect_container_copy_specs() self._collect_tailed_files() self._collect_strings() self._collect_cmds() self._collect_manual() self._log_debug(f"collected plugin '{self.name()}' in " f"{time() - start}") def get_description(self): """This function will return the description for the plugin""" try: return self.short_desc except Exception: return "" def check_enabled(self): """This method will be used to verify that a plugin should execute given the condition of the underlying environment. The default implementation will return True if none of class.files, class.packages, nor class.commands is specified. If any of these is specified the plugin will check for the existence of any of the corresponding paths, packages or commands and return True if any are present. For plugins with more complex enablement checks this method may be overridden. :returns: ``True`` if the plugin should be run for this system, else ``False`` :rtype: ``bool`` """ # some files or packages have been specified for this package if any([self.files, self.packages, self.commands, self.kernel_mods, self.services, self.containers, self.architectures]): if isinstance(self.files, str): self.files = [self.files] if isinstance(self.packages, str): self.packages = [self.packages] if isinstance(self.commands, str): self.commands = [self.commands] if isinstance(self.kernel_mods, str): self.kernel_mods = [self.kernel_mods] if isinstance(self.services, str): self.services = [self.services] return self._check_plugin_triggers(self.files, self.packages, self.commands, self.services, self.containers) return True def _check_plugin_triggers(self, files, packages, commands, services, containers): if not any([files, packages, commands, services, containers]): # no checks beyond architecture restrictions return self.check_is_architecture() return ((any(self.path_exists(fname) for fname in files) or any(self.is_installed(pkg) for pkg in packages) or any(is_executable(cmd, self.sysroot) for cmd in commands) or any(self.is_module_loaded(mod) for mod in self.kernel_mods) or any(self.is_service(svc) for svc in services) or any(self.container_exists(cntr) for cntr in containers)) and self.check_is_architecture()) def check_is_architecture(self): """Checks whether or not the system is running on an architecture that the plugin allows. If not architecture is set, assume plugin can run on all arches. :returns: ``True`` if the host's architecture allows the plugin to run, else ``False`` :rtype: ``bool`` """ if self.architectures is None: return True regex = f'(?:{"|".join(self.architectures)})' return re.match(regex, self.policy.get_arch()) def default_enabled(self): """This decides whether a plugin should be automatically loaded or only if manually specified in the command line.""" return True def add_default_collections(self): """Based on the class attrs defined for plugin enablement, add a standardized set of collections before we call the plugin's own setup() method. """ # For any service used for enablement checks, collect its current # status if it exists for service in self.services: if self.is_service(service): self.add_service_status(service) self.add_journal(service) for kmod in self.kernel_mods: if self.is_module_loaded(kmod): self.add_cmd_output(f"modinfo {kmod}") def setup(self): """Collect the list of files declared by the plugin. This method may be overridden to add further copy_specs, forbidden_paths, and external programs if required. """ self.add_copy_spec(list(self.files)) def setup_verify(self): if not hasattr(self, "verify_packages"): if hasattr(self, "packages") and self.packages: # Limit automatic verification to only the named packages self.verify_packages = [p + "$" for p in self.packages] else: return pm = self.policy.package_manager verify_cmd = pm.build_verify_command(self.verify_packages) if verify_cmd: self.add_cmd_output(verify_cmd) def path_exists(self, path): """Helper to call the sos.utilities wrapper that allows the corresponding `os` call to account for sysroot :param path: The canonical path for a specific file/directory :type path: ``str`` :returns: True if the path exists in sysroot, else False :rtype: ``bool`` """ return path_exists(path, self.sysroot) def path_isdir(self, path): """Helper to call the sos.utilities wrapper that allows the corresponding `os` call to account for sysroot :param path: The canonical path for a specific file/directory :type path: ``str`` :returns: True if the path is a dir, else False :rtype: ``bool`` """ return path_isdir(path, self.sysroot) def path_isfile(self, path): """Helper to call the sos.utilities wrapper that allows the corresponding `os` call to account for sysroot :param path: The canonical path for a specific file/directory :type path: ``str`` :returns: True if the path is a file, else False :rtype: ``bool`` """ return path_isfile(path, self.sysroot) def path_islink(self, path): """Helper to call the sos.utilities wrapper that allows the corresponding `os` call to account for sysroot :param path: The canonical path for a specific file/directory :type path: ``str`` :returns: True if the path is a link, else False :rtype: ``bool`` """ return path_islink(path, self.sysroot) def listdir(self, path): """Helper to call the sos.utilities wrapper that allows the corresponding `os` call to account for sysroot :param path: The canonical path for a specific file/directory :type path: ``str`` :returns: Contents of path, if it is a directory :rtype: ``list`` """ return listdir(path, self.sysroot) def path_join(self, path, *p): """Helper to call the sos.utilities wrapper that allows the corresponding `os` call to account for sysroot :param path: The leading path passed to os.path.join() :type path: ``str`` :param p: Following path section(s) to be joined with ``path``, an empty parameter will result in a path that ends with a separator :type p: ``str`` """ return path_join(path, *p, sysroot=self.sysroot) def postproc(self): """Perform any postprocessing. To be replaced by a plugin if required. """ pass def check_process_by_name(self, process): """Checks if a named process is found in /proc/[0-9]*/cmdline. :param process: The name of the process :type process: ``str`` :returns: ``True`` if the process exists, else ``False`` :rtype: ``bool`` """ status = False cmd_line_glob = "/proc/[0-9]*/cmdline" try: cmd_line_paths = glob.glob(cmd_line_glob) for path in cmd_line_paths: with open(self.path_join(path), 'r', encoding='utf-8') as pfile: cmd_line = pfile.read().strip() if process in cmd_line: status = True except IOError: return False return status def get_process_pids(self, process): """Get a list of all PIDs that match a specified name :param process: The name of the process the get PIDs for :type process: ``str`` :returns: A list of PIDs :rtype: ``list`` """ pids = [] cmd_line_glob = "/proc/[0-9]*/cmdline" cmd_line_paths = glob.glob(cmd_line_glob) for path in cmd_line_paths: try: with open(path, 'r', encoding='utf-8') as f: cmd_line = f.read().strip() if process in cmd_line: pids.append(path.split("/")[2]) except IOError: continue return pids def get_network_namespaces(self, ns_pattern=None, ns_max=None): if ns_max is None and self.commons['cmdlineopts'].namespaces: ns_max = self.commons['cmdlineopts'].namespaces return self.filter_namespaces(self.commons['namespaces']['network'], ns_pattern, ns_max) def filter_namespaces(self, ns_list, ns_pattern=None, ns_max=None): """Filter a list of namespaces by regex pattern or max number of namespaces (options originally present in the networking plugin.) """ out_ns = [] pattern = None # Regex initialization outside of for loop if ns_pattern: pattern = ( f'(?:{"$|".join(ns_pattern.split()).replace("*", ".*")}$)') for ns in ns_list: # if ns_pattern defined, skip namespaces not matching the pattern if ns_pattern and not bool(re.match(pattern, ns)): continue out_ns.append(ns) # if ns_max is defined at all, break the loop when the limit is # reached # this allows the use of both '0' and `None` to mean unlimited if ns_max: if len(out_ns) == ns_max: self._log_warn("Limiting namespace iteration " f"to first {ns_max} namespaces found") break return out_ns class PluginDistroTag(): """The base tagging class for distro-specific classes used to signify that a Plugin is written for that distro. Use IndependentPlugin for plugins that are distribution agnostic """ class RedHatPlugin(PluginDistroTag): """Tagging class for Red Hat's Linux distributions""" class UbuntuPlugin(PluginDistroTag): """Tagging class for Ubuntu Linux""" class DebianPlugin(PluginDistroTag): """Tagging class for Debian Linux""" class SuSEPlugin(PluginDistroTag): """Tagging class for SuSE Linux distributions""" class OpenEulerPlugin(PluginDistroTag): """Tagging class for openEuler linux distributions""" class CosPlugin(PluginDistroTag): """Tagging class for Container-Optimized OS""" class IndependentPlugin(PluginDistroTag): """Tagging class for plugins that can run on any platform""" class ExperimentalPlugin(PluginDistroTag): """Tagging class that indicates that this plugin is experimental""" class AzurePlugin(PluginDistroTag): """Tagging class for Azure Linux""" def import_plugin(name, superclasses=None): """Import name as a module and return a list of all classes defined in that module. superclasses should be a tuple of valid superclasses to import, this defaults to (Plugin,). """ plugin_fqname = f"sos.report.plugins.{name}" if not superclasses: superclasses = (Plugin,) return import_module(plugin_fqname, superclasses) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/omnipath_client.py0000664000175000017500000000377114660147624020326 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Pavel Moravec # 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. # 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 os.path import join from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class OmnipathClient(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'OmniPath Tools and Fast Fabric Client' plugin_name = 'omnipath_client' profiles = ('hardware',) packages = ('opa-basic-tools',) def setup(self): self.add_cmd_output([ "opainfo", "opafabricinfo", "opahfirev", "opapmaquery", "opaportinfo", "opasaquery", "opasmaquery", "opashowmc", "opareports", ]) # opacapture generates a tarball of given name we should collect; # rather than storing it somewhere under /var/tmp and copying it via # add_copy_spec, add it directly to sos_commands/ dir by # building a path argument using self.get_cmd_output_path(). # This command calls 'depmod -a', so lets make sure we # specified the 'allow-system-changes' option before running it. if self.get_option('allow_system_changes'): opa_fullpath = join(self.get_cmd_output_path(), "opacapture.tgz") self.add_cmd_output(f"opacapture {opa_fullpath}", changes=True) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/selinux.py0000664000175000017500000000377514660147624016644 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin, PluginOpt class SELinux(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'SELinux access control' plugin_name = 'selinux' profiles = ('container', 'system', 'security', 'openshift') option_list = [ PluginOpt('fixfiles', default=False, desc='collect incorrect file context labels') ] packages = ('libselinux', 'selinux-utils') def setup(self): self.add_copy_spec([ '/etc/sestatus.conf', '/etc/selinux' ]) # capture this with a higher log limit since #2035 may limit this # collection self.add_copy_spec('/var/lib/selinux', sizelimit=50) self.add_cmd_output('sestatus') state = self.exec_cmd('getenforce')['output'] if state != 'Disabled': self.add_cmd_output([ 'ps auxZww', 'sestatus -v', 'sestatus -b', 'selinuxdefcon root', 'selinuxconlist root', 'selinuxexeccon /bin/passwd', 'semanage -o' # deprecated, may disappear at some point ]) subcmds = [ 'fcontext', 'user', 'port', 'login', 'node', 'interface', 'module' ] for subcmd in subcmds: self.add_cmd_output(f"semanage {subcmd} -l") if self.get_option('fixfiles'): self.add_cmd_output("restorecon -Rvn /", stderr=False, priority=100) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ubuntu.py0000664000175000017500000000366614660147624016476 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin, SoSPredicate from sos.utilities import is_executable class Ubuntu(Plugin, UbuntuPlugin): short_desc = 'Ubuntu specific information' plugin_name = 'ubuntu' profiles = ('system',) def setup(self): self.add_cmd_output([ "ubuntu-security-status --thirdparty --unavailable", "hwe-support-status --verbose", ]) if self.is_installed('ubuntu-advantage-tools'): if is_executable('ua'): ua_tools_status = 'ua status' elif is_executable('pro'): ua_tools_status = 'pro status' else: ua_tools_status = 'ubuntu-advantage status' ua_pred = SoSPredicate(self, kmods=['tls']) self.add_cmd_output(ua_tools_status, pred=ua_pred, changes=True) self.add_cmd_output(f"{ua_tools_status} --format json", pred=ua_pred, changes=True) if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/ubuntu-advantage.log", "/var/log/ubuntu-advantage.log.1", "/var/log/ubuntu-advantage.log.2*", "/var/log/ubuntu-advantage-timer.log", "/var/log/ubuntu-advantage-timer.log.1", "/var/log/ubuntu-advantage-timer.log.2*", ]) else: self.add_copy_spec("/var/log/ubuntu-advantage.log*") self.add_copy_spec("/var/log/ubuntu-advantage-timer.log*") sos-4.8.0/sos/report/plugins/dlm.py0000664000175000017500000000302314660147624015713 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Dlm(Plugin, IndependentPlugin): short_desc = 'DLM (Distributed lock manager)' plugin_name = "dlm" profiles = ("cluster", ) packages = ("cman", "dlm", "pacemaker") option_list = [ PluginOpt('lockdump', default=False, desc='capture lock dumps for DLM') ] def setup(self): self.add_copy_spec([ "/etc/sysconfig/dlm" ]) self.add_cmd_output([ "dlm_tool log_plock", "dlm_tool dump", "dlm_tool ls -n" ]) if self.get_option("lockdump"): self.do_lockdump() def do_lockdump(self): """ Do dlm lock dumps """ dlm_tool = "dlm_tool ls" result = self.collect_cmd_output(dlm_tool) if result["status"] != 0: return lock_exp = r'^name\s+([^\s]+)$' lock_re = re.compile(lock_exp, re.MULTILINE) for lockspace in lock_re.findall(result["output"]): self.add_cmd_output( f"dlm_tool lockdebug -svw '{lockspace}'", suggest_filename=f"dlm_locks_{lockspace}" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/openstack_ansible.py0000664000175000017500000000247714660147624020637 0ustar arifarif# Copyright (C) 2017 Major Hayden # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OpenStackAnsible(Plugin, IndependentPlugin): short_desc = 'OpenStack-Ansible' plugin_name = "openstack_ansible" profiles = ('openstack',) files = ('/etc/openstack_deploy/',) def setup(self): """Gathering the contents of the report.""" self.add_copy_spec([ "/etc/openstack_deploy/", "/etc/openstack-release", "/etc/rpc_deploy/", "/etc/rpc-release" ]) def postproc(self): """Remove sensitive keys and passwords from YAML files.""" secrets_files = [ "/etc/openstack_deploy/user_secrets.yml", "/etc/rpc_deploy/user_secrets.yml" ] regexp = r"^\s*#*([\w_]*:\s*).*" for secrets_file in secrets_files: self.do_path_regex_sub( secrets_file, regexp, r"\1*********") sos-4.8.0/sos/report/plugins/systemtap.py0000664000175000017500000000156114660147624017175 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Eugene Teo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class SystemTap(Plugin, IndependentPlugin): short_desc = 'SystemTap dynamic instrumentation' plugin_name = 'systemtap' profiles = ('debug', 'performance') commands = ('stap',) packages = ('systemtap', 'systemtap-runtime') def setup(self): self.add_cmd_output([ "stap -V 2", "uname -r", "stap-report" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/krb5.py0000664000175000017500000000560314660147624016010 0ustar arifarif# Copyright (C) 2013,2018 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re import socket from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Krb5(Plugin): """This plugin handles the collection of kerberos authentication config files and logging. Users should expect to see their krb5 config(s) in the final archive, along with krb5 logging and `klist` output. kdc configs and acls will also be collected from the distribution-spcecific kdc directory. """ short_desc = 'Kerberos authentication' plugin_name = 'krb5' profiles = ('identity', 'system') kdcdir = None def setup(self): self.add_copy_spec([ "/etc/krb5.conf", "/etc/krb5.conf.d/*", f"{self.kdcdir}/kadm5.acl", f"{self.kdcdir}/kdc.conf", "/var/log/kadmind.log" ]) self.collect_kinit() self.add_copy_spec("/var/log/krb5kdc.log", tags="kerberos_kdc_log") self.add_cmd_output(f"klist -ket {self.kdcdir}/.k5*") self.add_cmd_output("klist -ket /etc/krb5.keytab") def collect_kinit(self): """ Collect the kinit command output for the system with id_provider "AD" or "IPA" domains. While integrating the Linux M/c with AD the realmd will create a computer object on the AD side. The realmd and AD restrict the Hostname/SPN to 15 Characters. """ hostname = socket.getfqdn() sssd_conf = "/etc/sssd/sssd.conf" if self.path_isfile(sssd_conf): with open(sssd_conf, 'r', encoding='utf-8') as f: for line in f: if re.match(r'\s*id_provider\s*=\s*ad', line, re.IGNORECASE): hostname = hostname.split('.')[0][:15].upper() self.add_cmd_output(f"KRB5_TRACE=/dev/stdout \ kinit -k '{hostname}$'") break if re.match(r'\s*id_provider\s*=\s*ipa', line, re.IGNORECASE): self.add_cmd_output(f"KRB5_TRACE=/dev/stdout \ kinit -k '{hostname}'") break class RedHatKrb5(Krb5, RedHatPlugin): packages = ('krb5-libs', 'krb5-server') kdcdir = "/var/kerberos/krb5kdc" class UbuntuKrb5(Krb5, DebianPlugin, UbuntuPlugin): packages = ('krb5-kdc', 'krb5-config', 'krb5-user') kdcdir = "/var/lib/krb5kdc" # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/iscsi.py0000664000175000017500000000426414660147624016261 0ustar arifarif# Copyright (C) 2007-2012 Red Hat, Inc., Ben Turner # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Iscsi(Plugin): short_desc = 'iSCSI initiator' plugin_name = "iscsi" profiles = ('storage',) def setup(self): var_puppet_gen = "/var/lib/config-data/puppet-generated/iscsid" self.add_copy_spec([ "/etc/iscsi/iscsid.conf", "/etc/iscsi/initiatorname.iscsi", var_puppet_gen + "/etc/iscsi/initiatorname.iscsi", "/var/lib/iscsi" ]) self.add_cmd_output([ "iscsiadm -m session -P 3", "iscsiadm -m node -P 1", "iscsiadm -m iface -P 1", "iscsiadm -m node --op=show" ]) def postproc(self): # Example for scrubbing node.session.auth.password # # node.session.auth.password = jfaiu1nNQJcsa,sti4lho'jZia=ia # # to # # node.session.auth.password = ******** nodesessionpwd = r"(node\.session\.auth\.password(_in)?\s+=\s+)(\S+)" discoverypwd = ( r"(discovery\.sendtargets\.auth\.password(_in)?" r"\s+=\s+)(\S+)" ) repl = r"\1********\n" self.do_path_regex_sub('/etc/iscsi/iscsid.conf', nodesessionpwd, repl) self.do_path_regex_sub('/etc/iscsi/iscsid.conf', discoverypwd, repl) self.do_path_regex_sub( '/var/lib/iscsi/nodes/*/*/*', nodesessionpwd, repl) self.do_path_regex_sub( '/var/lib/iscsi/nodes/*/*/*', discoverypwd, repl) class RedHatIscsi(Iscsi, RedHatPlugin): """ RedHatPlugin's setup() will be invoked """ packages = ('iscsi-initiator-utils',) class DebianIscsi(Iscsi, DebianPlugin, UbuntuPlugin): packages = ('open-iscsi',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/keyutils.py0000664000175000017500000000157614660147624017023 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Keyutils(Plugin, RedHatPlugin): short_desc = 'Kernel key ring' plugin_name = 'keyutils' profiles = ('system', 'kernel', 'security', 'storage') packages = ('keyutils',) def setup(self): self.add_copy_spec([ "/etc/request-key.conf", "/etc/request-key.d", "/proc/key-users" ]) self.add_cmd_output("keyctl show") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_tripleo.py0000664000175000017500000000317714660147624020676 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Cedric Jeanneret # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OpenStackTripleO(Plugin, IndependentPlugin): short_desc = 'Installation information from OpenStack Installer' plugin_name = 'openstack_tripleo' profiles = ('openstack', 'openstack_controller', 'openstack_compute') packages = ('openstack-selinux',) tripleo_log_paths = [] def setup(self): # Notes: recursion is max 2 for container-puppet and tripleo-config # Those directories are present on all OpenStack nodes self.tripleo_log_paths = [ '/var/log/paunch.log', '/var/lib/container-puppet/', '/var/lib/tripleo-config/', '/var/lib/tripleo/', '/etc/puppet/hieradata/' ] self.add_copy_spec(self.tripleo_log_paths) def postproc(self): # Ensures we do not leak passwords from the tripleo-config and # hieradata locations. # Other locations don't have sensitive data. regexp = r'(".*(key|password|pass|secret|database_connection))' \ r'([":\s]+)(.*[^"])([",]+)' for path in self.tripleo_log_paths: self.do_path_regex_sub(path, regexp, r'\1\3*********\5') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovirt.py0000664000175000017500000002270114660147624016306 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Lev Veyde # Copyright (C) 2014 Red Hat, Inc., Sandro Bonazzola # Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # Copyright (C) 2010 Red Hat, Inc. # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import re import signal from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt from sos.utilities import is_executable # Class name must be the same as file name and method names must not change class Ovirt(Plugin, RedHatPlugin): short_desc = 'oVirt Engine' plugin_name = "ovirt" profiles = ('virt',) packages = ( 'ovirt-engine', 'ovirt-engine-dwh', 'ovirt-engine-reports', 'ovirt-engine-metrics', 'ovirt-engine-setup', 'ovirt-vmconsole', 'ovirt-scheduler-proxy', 'rhevm', 'rhevm-dwh', 'rhevm-reports' ) DB_PASS_FILES = re.compile( flags=re.VERBOSE, pattern=r"""^/etc/ (rhevm|ovirt-engine|ovirt-engine-dwh)/ (engine.conf|ovirt-engine-dwhd.conf) (\.d/.+.conf.*?)?$""" ) DEFAULT_SENSITIVE_KEYS = ( 'ENGINE_DB_PASSWORD:ENGINE_PKI_TRUST_STORE_PASSWORD:' 'ENGINE_PKI_ENGINE_STORE_PASSWORD:DWH_DB_PASSWORD' ) option_list = [ PluginOpt('jbosstrace', default=True, desc='Enable oVirt Engine JBoss stack trace collection'), PluginOpt('sensitive-keys', default=DEFAULT_SENSITIVE_KEYS, desc='Sensitive keys to be masked in post-processing'), PluginOpt('heapdump', default=False, desc='Collect heap dumps from /var/log/ovirt-engine/dump/') ] def setup(self): if self.get_option('jbosstrace') and self.is_installed('ovirt-engine'): engine_pattern = r"^ovirt-engine\ -server.*jboss-modules.jar" pgrep = f"pgrep -f '{engine_pattern}'" res = self.exec_cmd(pgrep) engine_pids = [int(x) for x in res['output'].splitlines()] if not engine_pids: self.soslog.error('Unable to get ovirt-engine pid') self.add_alert('Unable to get ovirt-engine pid') for pid in engine_pids: try: # backtrace written to '/var/log/ovirt-engine/console.log os.kill(pid, signal.SIGQUIT) except OSError: self.soslog.error(f'Unable to send signal to {pid}') self.add_forbidden_path([ '/etc/ovirt-engine/.pgpass', '/etc/rhevm/.pgpass' ]) if not self.get_option('heapdump'): self.add_forbidden_path('/var/log/ovirt-engine/dump') self.add_dir_listing('/var/log/ovirt-engine/dump/') certificates = [ '/etc/pki/ovirt-engine/ca.pem', '/etc/pki/ovirt-engine/apache-ca.pem', '/etc/pki/ovirt-engine/certs/engine.cer', '/etc/pki/ovirt-engine/certs/apache.cer', '/etc/pki/ovirt-engine/certs/websocket-proxy.cer', '/etc/pki/ovirt-engine/certs/jboss.cer', '/etc/pki/ovirt-engine/certs/imageio-proxy.cer', '/etc/pki/ovirt-engine/certs/ovirt-provider-ovn.cer', ] keystores = [ ('mypass', '/etc/pki/ovirt-engine/.truststore'), ('changeit', '/var/lib/ovirt-engine/external_truststore'), ] self.add_cmd_output([ # Copy all engine tunables and domain information "engine-config --all", # clearer diff from factory defaults (only on ovirt>=4.2.8) "engine-config -d", ]) self.add_cmd_output([ # process certificate files f"openssl x509 -in {c} -text -noout" for c in certificates ]) self.add_cmd_output([ # process TrustStore certificates f"keytool -list -storepass {p} -rfc -keystore {c}" for (p, c) in keystores ]) # 3.x line uses engine-manage-domains, 4.x uses ovirt-aaa-jdbc-tool manage_domains = 'engine-manage-domains' extensions_tool = 'ovirt-engine-extensions-tool' jdbc_tool = 'ovirt-aaa-jdbc-tool' if is_executable(manage_domains): self.add_cmd_output(f'{manage_domains} list') if is_executable(extensions_tool): self.add_cmd_output(f'{extensions_tool} info list-extensions') if is_executable('ovirt-aaa-jdbc-tool'): subcmds = [ 'query --what=user', 'query --what=group', 'settings show' ] self.add_cmd_output([f'{jdbc_tool} {sc}' for sc in subcmds]) # Copy engine config files. self.add_copy_spec([ "/etc/ovirt-engine", "/etc/rhevm/", "/etc/ovirt-engine-dwh", "/etc/ovirt-engine-reports", "/etc/ovirt-engine-metrics", "/etc/ovirt-engine-setup", "/etc/ovirt-vmconsole", "/var/log/ovirt-engine", "/var/log/ovirt-engine-dwh", "/var/log/ovirt-engine-reports", "/var/log/ovirt-scheduler-proxy", "/var/log/rhevm", "/etc/sysconfig/ovirt-engine", "/usr/share/ovirt-engine/conf", "/var/log/ovirt-guest-agent", "/var/lib/ovirt-engine/setup-history.txt", "/var/lib/ovirt-engine/setup/answers", "/var/lib/ovirt-engine/external_truststore", "/var/tmp/ovirt-engine/config", "/var/lib/ovirt-engine/jboss_runtime/config", "/var/lib/ovirt-engine-reports/jboss_runtime/config" ]) self.add_file_tags({ "/etc/ovirt-engine/engine.conf.d/.*": "ovirt_engine_confd", "/var/log/ovirt-engine/boot.log": "ovirt_engine_boot_log", "/var/log/ovirt-engine/console.log": "ovirt_engine_console_log" }) # Copying host certs; extra copy the hidden .truststore file self.add_forbidden_path([ "/etc/pki/ovirt-engine/keys", "/etc/pki/ovirt-engine/private" ]) self.add_copy_spec([ "/etc/pki/ovirt-engine/", "/etc/pki/ovirt-engine/.truststore", ]) def postproc(self): """ Obfuscate sensitive keys. """ for pro in ["/etc/ovirt-engine/engine-config/engine-config.properties", "/etc/rhevm/rhevm-config/rhevm-config.properties"]: self.do_file_sub( pro, r"(Password.type)=(.*)", r"\1=********" ) engine_files = ( 'ovirt-engine.xml', 'ovirt-engine_history/current/ovirt-engine.v1.xml', 'ovirt-engine_history/ovirt-engine.boot.xml', 'ovirt-engine_history/ovirt-engine.initial.xml', 'ovirt-engine_history/ovirt-engine.last.xml', ) for filename in engine_files: self.do_file_sub( f"/var/tmp/ovirt-engine/config/{filename}", r"()(.*)()", r"\1********\3" ) self.do_file_sub( "/etc/ovirt-engine/redhatsupportplugin.conf", r"(proxyPassword)=(.*)", r"\1=********" ) passwd_files = [ "logcollector.conf", "imageuploader.conf", "isouploader.conf" ] for conf_file in passwd_files: conf_path = self.path_join("/etc/ovirt-engine", conf_file) self.do_file_sub( conf_path, r"(passwd|pg-pass)=(.*)", r"\1=********" ) sensitive_keys = self.DEFAULT_SENSITIVE_KEYS # Handle --alloptions case which set this to True. keys_opt = self.get_option('sensitive-keys') if keys_opt and keys_opt is not True: sensitive_keys = keys_opt key_list = [x for x in sensitive_keys.split(':') if x] self.do_path_regex_sub( self.DB_PASS_FILES, fr'({"|".join(key_list)})=(.*)', r'\1=********' ) # Answer files contain passwords. # Replace all keys that have 'password' in them, instead of hard-coding # here the list of keys, which changes between versions. # Sadly, the engine admin password prompt name does not contain # 'password'... so neither does the env key. for item in ( 'password', 'OVESETUP_CONFIG_ADMIN_SETUP', ): self.do_path_regex_sub( r'/var/lib/ovirt-engine/setup/answers/.*', rf'(?P[^=]*{item}[^=]*)=.*', r'\g=********' ) # aaa profiles contain passwords protect_keys = [ "vars.password", "pool.default.auth.simple.password", "pool.default.ssl.truststore.password", "config.datasource.dbpassword" ] regexp = fr"(^\s*#*({'|'.join(protect_keys)})\s*=\s*)(.*)" self.do_path_regex_sub(r"/etc/ovirt-engine/aaa/.*\.properties", regexp, r"\1*********") # vim: expandtab tabstop=4 shiftwidth=4 sos-4.8.0/sos/report/plugins/monit.py0000664000175000017500000000327014660147624016271 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., # Pablo Iranzo Gomez # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, RedHatPlugin class Monit(Plugin, RedHatPlugin): short_desc = 'Monit monitoring daemon' packages = ('monit',) profiles = ('system',) plugin_name = 'monit' # Define configuration files monit_conf = glob("/etc/monit.d/*") monit_conf.append("/etc/monit.conf") monit_conf.append("/etc/monitrc") # Define log files monit_log = ["/var/log/monit.log"] def setup(self): self.add_cmd_output("monit status") self.add_copy_spec(self.monit_log + self.monit_conf) def postproc(self): # Post process the files included to scrub any # password or other sensitive data # usernames and emails are cleaned to not disclose any # confidential data for file in self.monit_conf: # Remove username:password from files self.do_file_sub(file, r"(allow) (.*):(.*)", r"\1 ********:********" ) # Remove MAILSERVER username/password self.do_file_sub(file, r"(username|password) (\w)+", r"\1 ********" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/lightdm.py0000664000175000017500000000216314660147624016573 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class LightDm(Plugin, IndependentPlugin): short_desc = 'Light Display Manager' packages = ('lightdm', ) profiles = ('desktop', ) services = ('lightdm', ) plugin_name = 'lightdm' def setup(self): self.add_copy_spec([ "/etc/lightdm/lightdm.conf", "/etc/lightdm/users.conf" ]) if not self.get_option("all_logs"): self.add_copy_spec("/var/log/lightdm/lightdm.log") self.add_copy_spec("/var/log/lightdm/x-0-greeter.log") self.add_copy_spec("/var/log/lightdm/x-0.log") else: self.add_copy_spec("/var/log/lightdm") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/lilo.py0000664000175000017500000000131414660147624016077 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class Lilo(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'Lilo bootloader' plugin_name = 'lilo' profiles = ('system', 'boot') packages = ('lilo',) def setup(self): self.add_copy_spec("/etc/lilo.conf") self.add_cmd_output("lilo -q") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dhcp.py0000664000175000017500000000203214660147624016054 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class Dhcp(Plugin): short_desc = 'DHCP daemon' plugin_name = "dhcp" profiles = ('network',) class RedHatDhcp(Dhcp, RedHatPlugin): files = ('/etc/rc.d/init.d/dhcpd',) packages = ('dhcp',) def setup(self): super().setup() self.add_copy_spec([ "/etc/dhcpd.conf", "/etc/dhcp" ]) class UbuntuDhcp(Dhcp, UbuntuPlugin): files = ('/etc/init.d/udhcpd',) packages = ('udhcpd',) def setup(self): super().setup() self.add_copy_spec([ "/etc/default/udhcpd", "/etc/udhcpd.conf" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/tigervnc.py0000664000175000017500000000325214660147624016764 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class TigerVNC(Plugin, RedHatPlugin): """ This plugin gathers information for VNC servers provided by the tigervnc package. This is explicitly for server-side collections, not clients. By default, this plugin will capture the contents of /etc/tigervnc, which may include usernames. If usernames are sensitive information for end users of sos, consider using the `--clean` option to obfuscate these names. """ short_desc = 'TigerVNC server configuration' plugin_name = 'tigervnc' packages = ('tigervnc-server',) def setup(self): self.add_copy_spec('/etc/tigervnc/') # service names are 'vncserver@$port' where $port is :1,, :2, etc... # however they are not reported via list-unit-files, only list-units vncs = self.exec_cmd( 'systemctl list-units --type=service --no-legend vncserver*' ) if vncs['status'] == 0: for serv in vncs['output'].splitlines(): vnc = serv.split() if not vnc: continue self.add_service_status(vnc[0]) self.add_journal(vnc[0]) self.add_cmd_output('vncserver -list') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_placement.py0000664000175000017500000001155314660147624021165 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Lee Yarwood # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackPlacement(Plugin): short_desc = 'OpenStack Placement' plugin_name = "openstack_placement" profiles = ('openstack', 'openstack_controller') containers = ('.*placement_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/placement" service_name = 'openstack-placement-api' apachepkg = None def setup(self): # collect commands output only if the openstack-placement-api service # is running in_container = self.container_exists('.*placement_api') if self.is_service_running(self.service_name) or in_container: placement_config = "" # if containerized we need to pass the config to the cont. if in_container: placement_config = "--config-dir " + self.var_puppet_gen + \ "/etc/placement/" self.add_cmd_output( "placement-manage " + placement_config + " db version", suggest_filename="placement-manage_db_version" ) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. " "Source the environment file for the user " "intended to connect to the OpenStack " "environment.") else: res = self.collect_cmd_output( "openstack resource provider list" ) if res['status'] == 0: resource_provider_list = res['output'] for provider in resource_provider_list.splitlines()[3:-1]: res_provider = provider.split()[1] sub_cmds = [ "inventory", "trait", "aggregate", ] self.add_cmd_output([ f"openstack resource provider {sub_cmd} list " f"{res_provider}" for sub_cmd in sub_cmds ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/placement/", "/var/log/containers/placement/", "/var/log/containers/httpd/placement-api/", f"/var/log/{self.apachepkg}*/placement*", ]) else: self.add_copy_spec([ "/var/log/placement/*.log", "/var/log/containers/placement/*.log", "/var/log/containers/httpd/placement-api/*log", f"/var/log/{self.apachepkg}*/placement*.log", ]) self.add_copy_spec([ "/etc/placement/", self.var_puppet_gen + "/etc/placement/", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf", self.var_puppet_gen + "/etc/httpd/conf/", self.var_puppet_gen + "/etc/httpd/conf.d/", self.var_puppet_gen + "/etc/httpd/conf.modules.d/*.conf", ]) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/placement/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/placement/*", regexp, subst ) def postproc(self): protect_keys = ["password", "memcache_secret_key"] connection_keys = ["database_connection", "slave_connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianPlacement(OpenStackPlacement, DebianPlugin, UbuntuPlugin): apachepkg = "apache2" packages = ( 'placement-common', 'placement-api', 'python3-placement', ) class RedHatPlacement(OpenStackPlacement, RedHatPlugin): apachepkg = "httpd" packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/mssql.py0000664000175000017500000000644714660147624016313 0ustar arifarif# Copyright (C) 2018 Red Hat, K.K., Takayoshi Tanaka # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class MsSQL(Plugin, RedHatPlugin): short_desc = 'Microsoft SQL Server on Linux' plugin_name = "mssql" profiles = ('services',) packages = ('mssql-server',) option_list = [ PluginOpt('mssql-conf', default='/var/opt/mssql/mssql.conf', desc='SQL server configuration file') ] def setup(self): mssql_conf = self.get_option('mssql-conf') # Pick error log file from mssql_conf. # Expecting the following format # ``` # [filelocation] # errorlogfile = /var/opt/mssql/log # [sqlagent] # errorlogfile = /var/opt/mssql/log/sqlagentstartup.log # [network] # kerberoskeytabfile = /var/opt/mssql/secrets/mssql.keytab # ``` section = '' # default values errorlogfile = '/var/opt/mssql/log' sqlagent_errorlogfile = '/var/opt/mssql/log/sqlagentstartup.log' kerberoskeytabfile = None try: with open(mssql_conf, 'r', encoding='UTF-8') as mfile: for line in mfile.read().splitlines(): if line.startswith('['): section = line continue words = line.split('=') if words[0].strip() == 'errorlogfile': if section == '[filelocation]': errorlogfile = words[1].strip() elif section == '[sqlagent]': sqlagent_errorlogfile = words[1].strip() elif (words[0].strip() == 'kerberoskeytabfile') and \ (section == '[network]'): kerberoskeytabfile = words[1].strip() except IOError as ex: self._log_error(f'Could not open conf file {mssql_conf}: {ex}') return # Collect AD authentication configuratoin keytab_err = ('keytab file is specfieid in mssql_conf' f' but not found in {kerberoskeytabfile}') if kerberoskeytabfile is not None: if self.path_isfile(kerberoskeytabfile): self.add_dir_listing(kerberoskeytabfile) self.add_cmd_output(f'klist -e -k {kerberoskeytabfile}') else: self._log_error(keytab_err) # Expecting mssql_conf doesn't includeno sensitive information. self.add_copy_spec([ mssql_conf, errorlogfile + '/*', sqlagent_errorlogfile ]) if not self.get_option('all_logs'): self.add_copy_spec(errorlogfile + '/*') self.add_copy_spec(sqlagent_errorlogfile) else: self.add_copy_spec(errorlogfile + '/*') self.add_copy_spec(sqlagent_errorlogfile) self.add_journal(units=['mssql-server']) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_aodh.py0000664000175000017500000000767014660147624020135 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc., Sachin Patil # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackAodh(Plugin): short_desc = 'OpenStack Alarm service' plugin_name = "openstack_aodh" profiles = ('openstack', 'openstack_controller') var_puppet_gen = "/var/lib/config-data/puppet-generated/aodh" apachepkg = None def setup(self): self.add_copy_spec([ "/etc/aodh/", self.var_puppet_gen + "/etc/aodh/*", self.var_puppet_gen + "/etc/httpd/conf/*", self.var_puppet_gen + "/etc/httpd/conf.d/*", self.var_puppet_gen + "/etc/httpd/conf.modules.d/wsgi.conf", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf" ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/aodh/*", f"/var/log/{self.apachepkg}*/aodh*", ]) else: self.add_copy_spec([ "/var/log/aodh/*.log", f"/var/log/{self.apachepkg}*/aodh*.log", ]) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD', 'OS_AUTH_TYPE' ]] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME' ]] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. Source " "the environment file for the user intended " "to connect to the OpenStack environment.") else: self.add_cmd_output([ "aodh --version", "aodh capabilities list", "aodh alarm list" ]) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub( "/etc/aodh/aodh.conf", regexp, subst ) self.do_path_regex_sub( self.var_puppet_gen + "/etc/aodh/aodh.conf", regexp, subst ) def postproc(self): protect_keys = [ "admin_password", "connection_password", "host_password", "os_password", "password", "qpid_password", "rabbit_password", "memcache_secret_key" ] connection_keys = ["connection", "backend_url", "transport_url"] self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) join_con_keys = '|'.join(connection_keys) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianOpenStackAodh(OpenStackAodh, DebianPlugin, UbuntuPlugin): apachepkg = "apache2" packages = ( 'aodh-api', 'aodh-common', 'aodh-evaluator', 'aodh-notifier', 'aodh-listener', 'python-aodh', 'python3-aodh', ) class RedHatOpenStackAodh(OpenStackAodh, RedHatPlugin): apachepkg = "httpd" packages = ('openstack-selinux',) def setup(self): super().setup() if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/containers/httpd/aodh-api/*", "/var/log/containers/aodh/*" ]) else: self.add_copy_spec([ "/var/log/containers/httpd/aodh-api/*.log", "/var/log/containers/aodh/*.log" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/hpssm.py0000664000175000017500000000522514660147624016277 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Hpssm(Plugin, IndependentPlugin): """ This plugin will capture details for each controller from Smart Storage Array Administrator, an Array diagnostic report from Smart Storage Administrator Diagnostics Utility and, when the plugins debug option is enabled will gather the Active Health System log via the RESTful Interface Tool (iLOREST). """ short_desc = 'HP Smart Storage Management' plugin_name = 'hpssm' profiles = ('system', 'storage', 'hardware',) packages = ('ilorest', 'ssacli', 'ssaducli',) option_list = [ PluginOpt('debug', default=False, desc='capture debug data') ] def setup(self): cmd = 'ssacli' subcmds = [ 'ctrl all show status' ] slot_subcmds = [ 'array all show detail', 'ld all show', 'ld all show detail', 'pd all show', 'pd all show detail', 'show detail' ] self.add_cmd_output( [f"{cmd} {subcmd}" for subcmd in subcmds] ) pattern = re.compile("^HP[E] (.*) in Slot ([0123456789]+)") config_detail_cmd = cmd + ' ctrl all show config detail' config_detail = self.collect_cmd_output(config_detail_cmd) ctrl_slots = [] if config_detail['status'] == 0: ctrl_slots = [m.group(2) for line in config_detail['output'].splitlines() for m in [pattern.search(line)] if m] ssacli_ctrl_slot_cmd = cmd + ' ctrl slot=' self.add_cmd_output( [f"{ssacli_ctrl_slot_cmd}{slot} {slot_subcmd}" for slot in ctrl_slots for slot_subcmd in slot_subcmds] ) logpath = self.get_cmd_output_path() self.add_cmd_output( f'ssaducli -v -adu -f {logpath}/adu-log.zip', suggest_filename='ssaducli_-v_-adu.log' ) if self.get_option("debug"): self.do_debug(logpath) def do_debug(self, logpath): """ Collect debug logs """ self.add_cmd_output( f'ilorest serverlogs --selectlog=AHS --directorypath={logpath}', runat=logpath, suggest_filename='ilorest.log' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kvm.py0000664000175000017500000000167214660147624015744 0ustar arifarif# Copyright (C) 2009 Red Hat, Inc., Joey Boggs # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Kvm(Plugin, IndependentPlugin): short_desc = 'Kernel virtual machine' plugin_name = 'kvm' profiles = ('system', 'virt') files = ('/dev/kvm',) def setup(self): self.add_copy_spec([ "/sys/module/kvm/srcversion", "/sys/module/kvm_intel/srcversion", "/sys/module/kvm_amd/srcversion", "/sys/module/ksm/srcversion" ]) self.add_cmd_output("kvm_stat --once") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/powerpath.py0000664000175000017500000000351214660147624017153 0ustar arifarif# Copyright (C) 2008 EMC Corporation. Keith Kearnan # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class PowerPath(Plugin, RedHatPlugin): short_desc = 'EMC PowerPath' plugin_name = 'powerpath' profiles = ('storage', 'hardware') packages = ('EMCpower',) kernel_mods = ('emcp', 'emcpdm', 'emcpgpx', 'emcpmpx') def get_pp_files(self): """ EMC PowerPath specific information - files """ self.add_cmd_output("powermt version") self.add_copy_spec([ "/etc/init.d/PowerPath", "/etc/powermt.custom", "/etc/emcp_registration", "/etc/emc/mpaa.excluded", "/etc/emc/mpaa.lams", "/etc/emcp_devicesDB.dat", "/etc/emcp_devicesDB.idx", "/etc/emc/powerkmd.custom", "/etc/modprobe.conf.pp" ]) def get_pp_config(self): """ EMC PowerPath specific information - commands """ self.add_cmd_output([ "powermt display", "powermt display dev=all", "powermt check_registration", "powermt display options", "powermt display ports", "powermt display paths", "powermt dump" ]) def setup(self): self.get_pp_files() # If PowerPath is running collect additional PowerPath specific # information if self.path_isdir("/proc/emcp"): self.get_pp_config() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dmraid.py0000664000175000017500000000244714660147624016410 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Dmraid(Plugin, IndependentPlugin): short_desc = 'dmraid software RAID' plugin_name = 'dmraid' profiles = ('hardware', 'storage') packages = ('dmraid',) option_list = [ PluginOpt('metadata', default=False, desc='collect dmraid metadata') ] # V - {-V/--version} # b - {-b|--block_devices} # r - {-r|--raid_devices} # s - {-s|--sets} # t - [-t|--test] # a - {-a|--activate} {y|n|yes|no} # D - [-D|--dump_metadata] dmraid_options = ['V', 'b', 'r', 's', 'tay'] def setup(self): for opt in self.dmraid_options: self.add_cmd_output(f"dmraid -{opt}") if self.get_option("metadata"): metadata_path = self.get_cmd_output_path("metadata") self.add_cmd_output("dmraid -rD", runat=metadata_path, chroot=self.tmp_in_sysroot()) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/system.py0000664000175000017500000000244214660147624016467 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class System(Plugin, IndependentPlugin): short_desc = 'core system information' plugin_name = "system" profiles = ('system', 'kernel') verify_packages = ('glibc', 'initscripts', 'zlib') def setup(self): self.add_copy_spec([ "/proc/sys", "/etc/sysconfig", "/etc/default", "/etc/environment", ]) self.add_forbidden_path([ "/proc/sys/net/ipv4/route/flush", "/proc/sys/net/ipv6/route/flush", "/proc/sys/net/ipv6/neigh/*/retrans_time", "/proc/sys/net/ipv6/neigh/*/base_reachable_time", "/etc/default/grub.d/50-curtin-settings.cfg", ]) # collect glibc tuning decisions self.add_cmd_output([ "ld.so --help", "ld.so --list-diagnostics", "ld.so --list-tunables" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/opendaylight.py0000664000175000017500000000260514660147624017633 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OpenDaylight(Plugin, RedHatPlugin): short_desc = 'OpenDaylight network manager' plugin_name = 'opendaylight' profiles = ('openstack', 'openstack_controller') packages = ('opendaylight', 'puppet-opendaylight') var_puppet_gen = "/var/lib/config-data/puppet-generated/opendaylight" def setup(self): self.add_copy_spec([ "/opt/opendaylight/etc/", self.var_puppet_gen + "/opt/opendaylight/etc/", ]) if self.get_option("all_logs"): # Oxygen-SR3 and earlier versions, and may be removed in a future # update. self.add_copy_spec([ "/opt/opendaylight/data/log/", ]) else: # Oxygen-SR3 and earlier versions, and may be removed in a future # update. self.add_copy_spec([ "/opt/opendaylight/data/log/*.log*", ]) self.add_cmd_output("docker logs opendaylight_api") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/numa.py0000664000175000017500000000272414660147624016106 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Numa(Plugin, IndependentPlugin): short_desc = 'NUMA state and configuration' plugin_name = 'numa' profiles = ('hardware', 'system', 'memory', 'performance') packages = ('numad', 'numactl') def setup(self): numa_path = "/sys/devices/system/node" self.add_file_tags({ "%s/node.*/cpulist": 'numa_cpus' }) self.add_copy_spec([ "/etc/numad.conf", "/etc/logrotate.d/numad" ]) self.add_copy_spec("/var/log/numad.log*") self.add_cmd_output([ "numastat", "numastat -m", "numastat -n", "numactl --show", "numactl --hardware", ]) self.add_copy_spec([ self.path_join(numa_path, "node*/meminfo"), self.path_join(numa_path, "node*/cpulist"), self.path_join(numa_path, "node*/distance"), self.path_join(numa_path, "node*/hugepages/hugepages-*/*") ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kafka.py0000664000175000017500000000410514660147624016216 0ustar arifarif# Copyright (C) 2024 Alejandro Santoyo # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, UbuntuPlugin class Kafka(Plugin, UbuntuPlugin): """ This plugin collects log and configuration files, and also basic installation information (e.g., `snap info`) for Apache Kafka. """ short_desc = 'Apache Kafka plugin' plugin_name = 'kafka' profiles = ('services',) packages = ('charmed-kafka',) services = ('kafka',) def setup(self): log_file_pattern = "*.log*" if self.get_option("all_logs") else "*.log" if self.is_snap: self.add_cmd_output('snap info charmed-kafka') log_path = "/var/snap/charmed-kafka/common/var/log/kafka/" config_path = "/var/snap/charmed-kafka/current/etc/kafka/" else: log_path = "/usr/local/kafka/logs/" config_path = "/usr/local/kafka/config/" self.add_copy_spec([ log_path + log_file_pattern, config_path, ]) def postproc(self): protect_keys = ["password", "username",] config_path = ( "/var/snap/charmed-kafka/current/etc/kafka/" if self.is_snap else "/usr/local/kafka/config/" ) # get the absolute paths for all files in the config dir # (considering nested directories) and run do_path_regex_sub() # on each file to obfuscate the keys in protect_keys regexp = fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)" for root, _, files in os.walk(config_path): for file in files: self.do_path_regex_sub(os.path.join(root, file), regexp, r"\1*********") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/gdm.py0000664000175000017500000000133714660147624015714 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Gdm(Plugin, IndependentPlugin): short_desc = 'GNOME display manager' plugin_name = 'gdm' profiles = ('desktop',) packages = ('gdm', 'gdm3',) services = ('gdm',) def setup(self): self.add_copy_spec([ "/etc/gdm/*", "/etc/gdm3/*" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nss.py0000664000175000017500000000156514660147624015753 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class NSS(Plugin, IndependentPlugin): short_desc = 'Network Security Services configuration' plugin_name = "nss" profiles = ('network', 'security') packages = ('nss',) verify_packages = ('nss.*',) def setup(self): self.add_forbidden_path([ "/etc/pki/nssdb/cert*", "/etc/pki/nssdb/key*", "/etc/pki/nssdb/secmod.db" ]) self.add_copy_spec("/etc/pki/nssdb/pkcs11.txt") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/libraries.py0000664000175000017500000000310114660147624017110 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Libraries(Plugin, IndependentPlugin): short_desc = 'Dynamic shared libraries' plugin_name = 'libraries' profiles = ('system',) option_list = [ PluginOpt('ldconfigv', default=False, desc='collect verbose ldconfig output') ] def setup(self): self.add_copy_spec(["/etc/ld.so.conf", "/etc/ld.so.conf.d"]) if self.get_option("ldconfigv"): self.add_cmd_output("ldconfig -v -N -X") self.add_env_var([ 'PATH', 'LD_LIBRARY_PATH', 'LD_PRELOAD' ]) ldconfig = self.collect_cmd_output("ldconfig -p -N -X") if ldconfig['status'] == 0: # Collect library directories from ldconfig's cache dirs = set() for lib in ldconfig['output'].splitlines(): fqlib = lib.split(" => ", 2) if len(fqlib) != 2: continue dirs.add(fqlib[1].rsplit('/', 1)[0]) if dirs: self.add_dir_listing( f"{' '.join(dirs)}", suggest_filename='ld_so_cache' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/activemq.py0000664000175000017500000000323614660147624016756 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class ActiveMq(Plugin, DebianPlugin): short_desc = 'ActiveMQ message broker' plugin_name = 'activemq' profiles = ('openshift',) packages = ('activemq', 'activemq-core') files = ('/var/log/activemq',) def setup(self): if self.get_option("all_logs"): self.add_copy_spec(list(self.files)) else: self.add_copy_spec([ "/var/log/activemq/activemq.log", "/var/log/activemq/wrapper.log" ]) def postproc(self): # activemq.xml contains credentials in this form: # self.do_file_sub( '/etc/activemq/activemq.xml', r'(\s*password=")[^"]*(".*)', r"\1******\2" ) class RedHatActiveMq(ActiveMq, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec([ '/etc/sysconfig/activemq', '/etc/activemq/activemq.xml' ]) class UbuntuActiveMq(ActiveMq, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec([ '/etc/activemq', '/etc/default/activemq' ]) sos-4.8.0/sos/report/plugins/ssh.py0000664000175000017500000000567614660147624015754 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Eugene Teo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import pwd from glob import glob from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Ssh(Plugin, IndependentPlugin): short_desc = 'Secure shell service' plugin_name = 'ssh' profiles = ('services', 'security', 'system', 'identity') option_list = [ PluginOpt('userconfs', default=True, val_type=str, desc=('Changes whether module will ' 'collect user .ssh configs')) ] def setup(self): self.add_file_tags({ '/etc/ssh/sshd_config$': 'sshd_config', '/etc/ssh/ssh_config$': 'ssh_config' }) sshcfgs = [ "/etc/ssh/ssh_config", "/etc/ssh/sshd_config", "/etc/ssh/sshd_config.d/*", ] # Include main config files self.add_copy_spec(sshcfgs) self.included_configs(sshcfgs) # If userconfs option is set to False, skips this if self.get_option('userconfs'): self.user_ssh_files_permissions() def included_configs(self, sshcfgs): """ Include subconfig files """ # Read configs for any includes and copy those try: cfgfiles = [ f for files in [ glob(copyspec, recursive=True) for copyspec in sshcfgs ] for f in files ] for sshcfg in cfgfiles: tag = sshcfg.split('/')[-1] with open(self.path_join(sshcfg), 'r', encoding='UTF-8') as cfgfile: for line in cfgfile: # skip empty lines and comments if len(line.split()) == 0 or line.startswith('#'): continue # ssh_config keywords are allowed as case-insensitive if line.lower().startswith('include'): confarg = line.split() self.add_copy_spec(confarg[1], tags=tag) except Exception: # pylint: disable=broad-except pass def user_ssh_files_permissions(self): """ Iterate over .ssh folders in user homes to see their permissions. Bad permissions can prevent SSH from allowing access to given user. """ users_data = pwd.getpwall() # Read the home paths of users in the system and check the ~/.ssh dirs for user in users_data: home_dir = self.path_join(user.pw_dir, '.ssh') self.add_dir_listing(home_dir) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dracut.py0000664000175000017500000000165614660147624016433 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Dracut(Plugin, RedHatPlugin): short_desc = 'Dracut initramfs generator' plugin_name = "dracut" packages = ("dracut",) profiles = ("boot",) def setup(self): self.add_copy_spec([ "/etc/dracut.conf", "/etc/dracut.conf.d" ]) self.add_cmd_output([ "dracut --list-modules", "dracut --print-cmdline" ], env={"RPMOSTREE_CLIWRAP_SKIP": "true"}) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/virsh.py0000664000175000017500000000741314660147624016301 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class LibvirtClient(Plugin, IndependentPlugin): short_desc = 'client for libvirt virtualization API' plugin_name = 'virsh' profiles = ('system', 'virt') packages = ('libvirt-client',) def setup(self): # virt-manager logs self.add_copy_spec([ "/root/.cache/virt-manager/*.log", "/root/.virt-manager/*.log" ]) cmd = 'virsh -r' # get host information subcmds = [ 'domcapabilities', 'capabilities', 'nodeinfo', 'freecell --all', 'node-memory-tune', 'version', 'pool-capabilities', 'nodecpumap', 'maxvcpus kvm', 'sysinfo', 'nodedev-list --tree', ] for subcmd in subcmds: self.add_cmd_output(f'{cmd} {subcmd}', foreground=True) self.add_cmd_output(f"{cmd} list --all", tags="virsh_list_all", foreground=True) # get network, pool and nwfilter elements for k in ['net', 'nwfilter', 'pool']: k_list = self.collect_cmd_output(f'{cmd} {k}-list %s' % ('--all' if k in ['net', 'pool'] else ''), foreground=True) if k_list['status'] == 0: k_lines = k_list['output'].splitlines() # the 'Name' column position changes between virsh cmds # catch the rare exceptions when 'Name' is not found try: pos = k_lines[0].split().index('Name') except Exception: # pylint: disable=broad-except continue for j in filter(lambda x: x, k_lines[2:]): name = j.split()[pos] self.add_cmd_output(f'{cmd} {k}-dumpxml {name}', foreground=True) # cycle through the VMs/domains list, ignore 2 header lines and latest # empty line, and dumpxml domain name in 2nd column domains_output = self.exec_cmd(f'{cmd} list --all', foreground=True) if domains_output['status'] == 0: domains_lines = domains_output['output'].splitlines()[2:] for domain in filter(lambda x: x, domains_lines): domain = domain.split()[1] for opt in ['dumpxml', 'dominfo', 'domblklist']: self.add_cmd_output(f'{cmd} {opt} {domain}', foreground=True) nodedev_output = self.exec_cmd(f"{cmd} nodedev-list", foreground=True) if nodedev_output['status'] == 0: for name in nodedev_output['output'].splitlines(): self.add_cmd_output( f"{cmd} nodedev-dumpxml {name}", foreground=True ) def postproc(self): match_exp = r"(\s*passwd\s*=\s*\")([^\"]*)(\".*)" virsh_path_exps = [ r"/root/\.cache/virt-manager/.*\.log", r"/root/\.virt-manager/.*\.log" ] for path_exp in virsh_path_exps: # Scrub passwords in virt-manager logs # Example of scrubbing: # # passwd="hackme" # To: # passwd="******" # self.do_path_regex_sub(path_exp, match_exp, r"\1******\3") # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/vulkan.py0000664000175000017500000000127414660147624016445 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Vulkan(Plugin, IndependentPlugin): short_desc = 'Vulkan' plugin_name = 'vulkan' profiles = ('hardware', 'desktop', 'gpu') files = ('/usr/bin/vulkaninfo',) def setup(self): self.add_cmd_output([ "vulkaninfo", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/tomcat.py0000664000175000017500000000402514660147624016431 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from datetime import datetime from sos.report.plugins import Plugin, RedHatPlugin class Tomcat(Plugin, RedHatPlugin): short_desc = 'Apache Tomcat Server' plugin_name = 'tomcat' profiles = ('webserver', 'java', 'services', 'sysmgmt') packages = ('tomcat', 'tomcat6', 'tomcat7', 'tomcat8', 'pki-servlet-engine') def setup(self): self.add_copy_spec([ "/etc/tomcat", "/etc/tomcat6", "/etc/tomcat7", "/etc/tomcat8" ]) if not self.get_option("all_logs"): log_glob = "/var/log/tomcat*/catalina.out" self.add_copy_spec(log_glob) # get today's date in iso format so that days/months below 10 # prepend 0 today = datetime.date(datetime.now()).isoformat() log_glob = f"/var/log/tomcat*/catalina.{today}.log" self.add_copy_spec(log_glob) else: self.add_copy_spec("/var/log/tomcat*/*") self.add_file_tags({ "/etc/tomcat.*/web.xml": "tomcat_web_xml", "/var/log/tomcat.*/catalina.out": "catalina_out", "/var/log/tomcat.*/catalina.*.log": "catalina_server_log" }) def postproc(self): server_password_attr = ['keyPass', 'keystorePass', 'truststorePass', 'SSLPassword'] self.do_path_regex_sub( r"\/etc\/tomcat.*\/server.xml", fr"({'|'.join(server_password_attr)})=(\S*)", r'\1="********"' ) self.do_path_regex_sub( r"\/etc\/tomcat.*\/tomcat-users.xml", r"(password)=(\S*)", r'\1="********"' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sysvipc.py0000664000175000017500000000156614660147624016651 0ustar arifarif# Copyright (C) 2007-2012 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class SysVIPC(Plugin, IndependentPlugin): short_desc = 'SysV IPC' plugin_name = "sysvipc" profiles = ('system', 'services') def setup(self): self.add_copy_spec([ "/proc/sysvipc/msg", "/proc/sysvipc/sem", "/proc/sysvipc/shm" ]) self.add_cmd_output([ "ipcs", "ipcs -u" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/qpid_dispatch.py0000664000175000017500000000427614660147624017766 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from socket import gethostname from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class QpidDispatch(Plugin, RedHatPlugin): short_desc = 'Qpid dispatch router' plugin_name = 'qpid_dispatch' profiles = ('services',) packages = ('qdrouterd', 'qpid-dispatch-tools', 'qpid-dispatch-router') option_list = [ PluginOpt('port', default='', val_type=int, desc='listening port to connect to'), PluginOpt('ssl-certificate', default='', val_type=str, desc='Path to file containing client SSL certificate'), PluginOpt('ssl-key', default='', val_type=str, desc='Path to file containing client SSL private key'), PluginOpt('ssl-trustfile', default='', val_type=str, desc='trusted CA database file') ] def setup(self): """ performs data collection for qpid dispatch router """ options = "" if self.get_option("port"): options = (options + " -b " + gethostname() + f":{self.get_option('port')}") # gethostname() is due to DISPATCH-156 # for either present option, add --option=value to 'options' variable for option in ["ssl-certificate", "ssl-key", "ssl-trustfile"]: if self.get_option(option): options = (options + f" --{option}=" + self.get_option(option)) self.add_cmd_output([ "qdstat -a" + options, # Show Router Addresses "qdstat -n" + options, # Show Router Nodes "qdstat -c" + options, # Show Connections "qdstat -m" + options # Show Broker Memory Stats ]) self.add_copy_spec([ "/etc/qpid-dispatch/qdrouterd.conf" ]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/nginx.py0000664000175000017500000000304114660147624016262 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Nginx(Plugin, IndependentPlugin): short_desc = 'nginx http daemon' plugin_name = "nginx" profiles = ('webserver',) packages = ('nginx',) option_list = [ PluginOpt('log', default=False, desc='collect all nginx logs') ] def setup(self): # collect configuration dump and build options self.add_cmd_output([ "nginx -V", "nginx -T" ]) # collect configuration files and only the current log set by default self.add_copy_spec([ "/etc/nginx/*", "/var/log/nginx/access.log", "/var/log/nginx/error.log", ]) # Other plugins collect these files; # do not collect them here to avoid collisions in the archive paths. altconf = [ 'automationcontroller', 'automationhub', 'automationedacontroller' ] self.add_forbidden_path([ f"/var/log/nginx/{alt}*" for alt in altconf ]) if self.get_option("log") or self.get_option("all_logs"): self.add_copy_spec("/var/log/nginx/*") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nscd.py0000664000175000017500000000173514660147624016076 0ustar arifarif# Copyright (C) 2007 Shijoe George # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Nscd(Plugin, IndependentPlugin): short_desc = 'Name service caching daemon' plugin_name = 'nscd' profiles = ('services', 'identity', 'system') files = ('/etc/nscd.conf',) packages = ('nscd',) def setup(self): self.add_copy_spec("/etc/nscd.conf") options = self.file_grep(r"^\s*logfile", "/etc/nscd.conf") if len(options) > 0: for opt in options: fields = opt.split() self.add_copy_spec(fields[1]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/infiniband.py0000664000175000017500000000524014660147624017243 0ustar arifarif# Copyright (C) 2011, 2012 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Infiniband(Plugin, IndependentPlugin): short_desc = 'Infiniband information' plugin_name = 'infiniband' profiles = ('hardware',) packages = ('libibverbs-utils', 'opensm', 'rdma', 'infiniband-diags') def setup(self): self.add_copy_spec([ "/etc/ofed/openib.conf", "/etc/ofed/opensm.conf", "/etc/rdma" ]) self.add_copy_spec("/var/log/opensm*") self.add_cmd_output([ "ibv_devices", "ibv_devinfo -v", "ibstat", "ibstatus", "ibswitches" ]) # run below commands for every IB device and its active port ports_cmds = [ "ibhosts", "iblinkinfo", "sminfo", "perfquery" ] ib_sysdir = "/sys/class/infiniband/" ib_devs = self.listdir(ib_sysdir) if self.path_isdir(ib_sysdir) else [] for ibdev in ib_devs: # Skip OPA hardware, as infiniband-diags tools does not understand # OPA specific MAD sent by opa-fm. Intel provides OPA specific # tools for OPA fabric diagnose. if ibdev.startswith("hfi"): continue for port in self.listdir(ib_sysdir + ibdev + "/ports"): # skip IWARP and RoCE devices lfile = ib_sysdir + ibdev + "/ports/" + port + "/link_layer" try: with open(lfile, 'r', encoding='UTF-8') as link_fp: link_layer = link_fp.readline() if link_layer != "InfiniBand\n": continue except IOError: continue sfile = ib_sysdir + ibdev + "/ports/" + port + "/state" try: with open(sfile, 'r', encoding='UTF-8') as state_fp: state = state_fp.readline() if not state.endswith(": ACTIVE\n"): continue except IOError: continue opts = f"-C {ibdev} -P {port}" self.add_cmd_output([f"{c} {opts}" for c in ports_cmds]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ssmtp.py0000664000175000017500000000172014660147624016307 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Ssmtp(Plugin, RedHatPlugin): short_desc = 'sSMTP information' plugin_name = 'ssmtp' profiles = ('mail', 'system') packages = ('ssmtp',) def setup(self): self.add_copy_spec([ "/etc/ssmtp/ssmtp.conf", "/etc/ssmtp/revaliases", "/etc/aliases" ]) def postproc(self): self.do_file_sub( '/etc/ssmtp/ssmtp.conf', r'(AuthPass)=(\S*)', r'\1=********' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/opensvc.py0000664000175000017500000000536514660147624016627 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Opensvc(Plugin, IndependentPlugin): short_desc = 'OpenSVC cluster and services (config and state collection)' plugin_name = 'opensvc' profiles = ('cluster', 'services', 'system') packages = ('opensvc',) def get_status(self, kind): """ Get the status of opensvc management service """ getobjs = self.collect_cmd_output(f"om {kind} ls --color=no") dirname = kind + '_status' if getobjs['status'] == 0: for line in getobjs['output'].splitlines(): self.add_cmd_output( f"om {line} print status --color=no", subdir=dirname ) def setup(self): self.add_copy_spec([ "/etc/opensvc/*", "/var/log/opensvc/*", "/etc/conf.d/opensvc", "/etc/default/opensvc", "/etc/sysconfig/opensvc", "/var/lib/opensvc/*.json", "/var/lib/opensvc/list.*", "/var/lib/opensvc/ccfg", "/var/lib/opensvc/cfg", "/var/lib/opensvc/certs/ca_certificates", "/var/lib/opensvc/certs/certificate_chain", "/var/lib/opensvc/compliance/*", "/var/lib/opensvc/namespaces/*", "/var/lib/opensvc/node/*", "/var/lib/opensvc/sec/*", "/var/lib/opensvc/svc/*", "/var/lib/opensvc/usr/*", "/var/lib/opensvc/vol/*", ]) self.add_cmd_output([ "om pool status --verbose --color=no", "om net status --verbose --color=no", "om mon --color=no", "om daemon dns dump --color=no", "om daemon relay status --color=no", "om daemon status --format flat_json --color=no" ]) self.add_dir_listing('/var/lib/opensvc', recursive=True) self.get_status('vol') self.get_status('svc') def postproc(self): # Example: # # [hb#2] # secret = mypassword # type = relay # timeout = 30 # # to # # [hb#2] # secret = **************************** # type = relay # timeout = 30 regexp = r"(\s*secret =\s*)\S+" self.do_file_sub( "/etc/opensvc/cluster.conf", regexp, r"\1****************************" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/apport.py0000664000175000017500000000257614660147624016460 0ustar arifarif# Copyright (c) 2012 Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, DebianPlugin, UbuntuPlugin class Apport(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'Apport crash reporting tool' plugin_name = 'apport' profiles = ('debug',) def setup(self): if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/apport.log", "/var/log/apport.log.1" ]) else: self.add_copy_spec("/var/log/apport*") self.add_copy_spec("/var/crash/**") self.add_copy_spec("/etc/apport/*") self.add_copy_spec("/var/lib/whoopsie/whoopsie-id") self.add_cmd_output( "gdbus call -y -d com.ubuntu.WhoopsiePreferences \ -o /com/ubuntu/WhoopsiePreferences \ -m com.ubuntu.WhoopsiePreferences.GetIdentifier") self.add_dir_listing('/var/crash', recursive=True) self.add_cmd_output("bash -c 'grep -B 50 -m 1 ProcMaps /var/crash/*'") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/wireless.py0000664000175000017500000000156614660147624017006 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate class Wireless(Plugin, IndependentPlugin): short_desc = 'Wireless Device Information' plugin_name = 'wireless' profiles = ('hardware', 'desktop', 'network') commands = ('iw', ) def setup(self): wireless_pred = SoSPredicate(self, kmods=['cfg80211']) self.add_cmd_output([ "iw list", "iw dev", "iwconfig", "iwlist scanning" ], pred=wireless_pred) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_mds.py0000664000175000017500000001022614660147624016724 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephMDS(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'CEPH mds' plugin_name = 'ceph_mds' profiles = ('storage', 'virt', 'container', 'ceph') containers = ('ceph-(.*-)?fs.*',) files = ('/var/lib/ceph/mds/*', '/var/lib/ceph/*/mds.*', '/var/snap/microceph/common/data/mds/*') def setup(self): all_logs = self.get_option("all_logs") microceph = self.policy.package_manager.pkg_by_name('microceph') if microceph: if all_logs: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-mds*.log*", ]) else: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-mds*.log", ]) self.add_forbidden_path([ "/var/snap/microceph/common/**/*keyring*", "/var/snap/microceph/current/**/*keyring*", "/var/snap/microceph/common/state/*", ]) else: self.add_file_tags({ '/var/log/ceph/ceph-mds.*.log': 'ceph_mds_log', }) if not all_logs: self.add_copy_spec(["/var/log/ceph/ceph-mds*.log",]) else: self.add_copy_spec(["/var/log/ceph/ceph-mds*.log*",]) self.add_copy_spec([ "/var/lib/ceph/bootstrap-mds/", "/var/lib/ceph/mds/", "/var/lib/ceph/*/mds.*", "/run/ceph/ceph-mds*", ]) self.add_forbidden_path([ "/etc/ceph/*keyring*", "/var/lib/ceph/*keyring*", "/var/lib/ceph/*/*keyring*", "/var/lib/ceph/*/*/*keyring*", "/var/lib/ceph/osd", "/var/lib/ceph/mon", # Excludes temporary ceph-osd mount location like # /var/lib/ceph/tmp/mnt.XXXX from sos collection. "/var/lib/ceph/tmp/*mnt*", "/etc/ceph/*bindpass*" ]) cmds = [ "cache status", "client ls", "config diff", "config show", "counter dump", "counter schema", "damage ls", "dump loads", "dump tree /", "dump_blocked_ops", "dump_historic_ops", "dump_historic_ops_by_duration", "dump_mempools", "dump_ops_in_flight", "get subtrees", "objecter_requests", "ops", "perf dump", "perf histogram dump", "perf histogram schema", "perf schema", "session ls", "status", "version", ] # If containerized, run commands in containers try: cname = self.get_all_containers_by_regex("ceph-mds*")[0][1] except Exception: # pylint: disable=broad-except cname = None directory = '/var/snap/microceph/current/run' if microceph \ else '/var/run/ceph' # common add_cmd_output for ceph and microceph self.add_cmd_output([ f"ceph daemon {i} {c}" for i in self.get_socks(directory) for c in cmds], container=cname ) def get_socks(self, directory): """ Find any available admin sockets under /var/run/ceph (or subdirs for later versions of Ceph) which can be used for ceph daemon commands """ ceph_sockets = [] for rdir, _, files in os.walk(directory): for file in files: if file.endswith('.asok') and 'mds' in file: ceph_sockets.append(self.path_join(rdir, file)) return ceph_sockets # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/conntrack.py0000664000175000017500000000457614660147624017137 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc., Marcus Linden # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, IndependentPlugin, SoSPredicate, PluginOpt) class Conntrack(Plugin, IndependentPlugin): short_desc = 'conntrack - netfilter connection tracking' plugin_name = 'conntrack' profiles = ('network', 'cluster') packages = ('conntrack-tools', 'conntrack', 'conntrackd') option_list = [ PluginOpt("namespaces", default=None, val_type=int, desc="Number of namespaces to collect, 0 for unlimited"), ] def setup(self): # Collect info from conntrackd self.add_copy_spec("/etc/conntrackd/conntrackd.conf") self.add_cmd_output([ "conntrackd -s network", "conntrackd -s cache", "conntrackd -s runtime", "conntrackd -s link", "conntrackd -s rsqueue", "conntrackd -s queue", "conntrackd -s ct", "conntrackd -s expect", ]) # Collect info from conntrack, only when all required kmods are loaded ct_pred = SoSPredicate(self, kmods=['nf_conntrack', 'nf_conntrack_netlink', 'nf_defrag_ipv4', 'nf_defrag_ipv6', 'nfnetlink'], required={'kmods': 'all'}) self.add_cmd_output([ "conntrack -L -o extended", "conntrack -S", ], pred=ct_pred) # Capture additional data from namespaces; each command is run # per-namespace cmd_prefix = "ip netns exec " nsps = self.get_option('namespaces') for namespace in self.get_network_namespaces(ns_max=nsps): ns_cmd_prefix = cmd_prefix + namespace + " " self.add_cmd_output(ns_cmd_prefix + "conntrack -L -o extended") self.add_cmd_output(ns_cmd_prefix + "conntrack -S") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ntp.py0000664000175000017500000000264614660147624015752 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Ntp(Plugin): short_desc = 'Network Time Protocol' plugin_name = "ntp" profiles = ('system', 'services') packages = ('ntp',) def setup(self): self.add_copy_spec("/etc/ntp.conf", tags="ntp_conf") self.add_copy_spec([ "/etc/ntp/step-tickers", "/etc/ntp/ntpservers" ]) self.add_cmd_output([ "ntptime", "ntpq -pn" ], cmd_as_tag=True) ids = self.collect_cmd_output('ntpq -c as') if ids['status'] == 0: for asid in [i.split()[1] for i in ids['output'].splitlines()[3:]]: self.add_cmd_output(f"ntpq -c 'rv {asid}'") class RedHatNtp(Ntp, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec("/etc/sysconfig/ntpd") self.add_cmd_output("ntpstat") class DebianNtp(Ntp, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec('/etc/default/ntp') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/devices.py0000664000175000017500000000134414660147624016565 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Devices(Plugin, IndependentPlugin): short_desc = 'devices specific commands' plugin_name = 'devices' profiles = ('system', 'hardware', 'boot', 'storage') packages = ('udev', 'systemd-udev') files = ('/dev',) def setup(self): self.add_cmd_output("udevadm info --export-db") # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/openstack_keystone.py0000664000175000017500000001163114660147624021053 0ustar arifarif# Copyright (C) 2013 Red Hat, Inc., Jeremy Agee # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class OpenStackKeystone(Plugin): short_desc = 'OpenStack Keystone' plugin_name = "openstack_keystone" profiles = ('openstack', 'openstack_controller') option_list = [ PluginOpt('nopw', default=True, desc='do not collect keystone passwords') ] var_puppet_gen = "/var/lib/config-data/puppet-generated/keystone" apachepkg = None domain_config_dir = "" def setup(self): self.add_copy_spec([ "/etc/keystone/default_catalog.templates", "/etc/keystone/keystone.conf", "/etc/keystone/logging.conf", "/etc/keystone/policy.json", self.var_puppet_gen + "/etc/keystone/*.conf", self.var_puppet_gen + "/etc/keystone/*.json", self.var_puppet_gen + "/etc/httpd/conf/", self.var_puppet_gen + "/etc/httpd/conf.d/", self.var_puppet_gen + "/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "/var/spool/cron/", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf" ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/keystone/", f"/var/log/{self.apachepkg}*/keystone*", ]) else: self.add_copy_spec([ "/var/log/keystone/*.log", f"/var/log/{self.apachepkg}*/keystone*.log", ]) # collect domain config directory, if specified # if not, collect default /etc/keystone/domains exec_out = self.collect_cmd_output( "crudini --get /etc/keystone/keystone.conf " "identity domain_config_dir") self.domain_config_dir = exec_out['output'] if exec_out['status'] != 0 or \ not self.path_isdir(self.domain_config_dir): self.domain_config_dir = "/etc/keystone/domains" self.add_copy_spec(self.domain_config_dir) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. Source " "the environment file for the user intended " "to connect to the OpenStack environment.") else: self.add_cmd_output("openstack endpoint list") self.add_cmd_output("openstack catalog list") self.add_file_tags({ ".*/etc/keystone/keystone.conf": "keystone_conf", "/var/log/keystone/keystone.log": "keystone_log" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/keystone/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/keystone/*", regexp, subst ) self.do_path_regex_sub( self.var_puppet_gen + "/etc/httpd/conf.d/", regexp, subst ) def postproc(self): protect_keys = [ "password", "qpid_password", "rabbit_password", "ssl_key_password", "ldap_dns_password", "neutron_admin_password", "host_password", "admin_password", "admin_token", "ca_password", "transport_url", "OIDCClientSecret", ] connection_keys = ["connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*(=\s*)?)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) # obfuscate LDAP plaintext passwords in domain config dir self.do_path_regex_sub( self.domain_config_dir, fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1********" ) class DebianKeystone(OpenStackKeystone, DebianPlugin, UbuntuPlugin): apachepkg = 'apache2' packages = ( 'keystone', 'python-keystone', 'python3-keystone', ) class RedHatKeystone(OpenStackKeystone, RedHatPlugin): apachepkg = 'httpd' packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_mgr.py0000664000175000017500000001347214660147624016734 0ustar arifarif# Copyright (C) 2023 Canonical Ltd., Nikhil Kshirsagar # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephMGR(Plugin, RedHatPlugin, UbuntuPlugin): """ This plugin is for capturing information from Ceph mgr nodes. While the majority of this plugin should be version-agnostic, several collections are dependent upon the version of Ceph installed. Versions that correlate to RHCS 4 or RHCS 5 are explicitly handled for differences such as those pertaining to log locations on the host filesystem. Note that while this plugin will activate based on the presence of Ceph containers, commands are run directly on the host as those containers are often not configured to successfully run the `ceph` commands collected by this plugin. These commands are majorily `ceph daemon` commands that will reference discovered admin sockets under /var/run/ceph. Users may expect to see several collections twice - once in standard output from the `ceph` command, and again in JSON format. The latter of which will be placed in the `json_output/` subdirectory within this plugin's directory in the report archive. These JSON formatted collections are intended to aid in automated analysis. """ short_desc = 'CEPH mgr' plugin_name = 'ceph_mgr' profiles = ('storage', 'virt', 'container', 'ceph') files = ('/var/lib/ceph/mgr/*', '/var/lib/ceph/*/mgr*', '/var/snap/microceph/common/data/mgr/*') containers = ('ceph-(.*-)?mgr.*',) def setup(self): all_logs = self.get_option("all_logs") microceph_pkg = self.policy.package_manager.pkg_by_name('microceph') ceph_mgr_cmds = ([ "balancer status", "healthcheck history ls", "log last cephadm", "mgr dump", "mgr metadata", "mgr module ls", "mgr stat", "mgr versions" ]) # if orchestrator is configured orch_configured = self.exec_cmd('ceph orch status') if orch_configured['status'] == 0: ceph_mgr_cmds += ([ "orch host ls", "orch device ls", "orch ls", "orch ls --export", "orch ps", "orch status --detail", "orch upgrade status" ]) cmds = [ "config diff", "config show", "counter dump", "counter schema", "dump_cache", "dump_mempools", "dump_osd_network", "mds_requests", "mds_sessions", "objecter_requests", "perf dump", "perf histogram dump", "perf histogram schema", "perf schema", "status", "version", ] directory = '' if not microceph_pkg: directory = '/var/run/ceph' self.add_file_tags({ '/var/log/ceph/(.*/)?ceph-mgr.*.log': 'ceph_mgr_log', }) self.add_forbidden_path([ "/etc/ceph/*keyring*", "/var/lib/ceph/**/*keyring*", "/var/lib/ceph/**/osd*", "/var/lib/ceph/**/mon*", # Excludes temporary ceph-osd mount location like # /var/lib/ceph/tmp/mnt.XXXX from sos collection. "/var/lib/ceph/**/tmp/*mnt*", "/etc/ceph/*bindpass*", ]) if not all_logs: self.add_copy_spec([ "/var/log/ceph/**/ceph-mgr*.log", ]) else: self.add_copy_spec([ "/var/log/ceph/**/ceph-mgr*.log*", ]) self.add_copy_spec([ "/var/lib/ceph/**/mgr*", "/var/lib/ceph/**/bootstrap-mgr/", "/run/ceph/**/ceph-mgr*", ]) else: directory = '/var/snap/microceph/current/run' self.add_file_tags({ '/var/snap/microceph/common/logs/ceph-mgr.*.log': 'ceph_mgr_log', }) self.add_forbidden_path([ "/var/snap/microceph/common/**/*keyring*", ]) if not all_logs: self.add_copy_spec([ "/var/snap/microceph/common/logs/ceph-mgr*.log", ]) else: self.add_copy_spec([ "/var/snap/microceph/common/logs/ceph-mgr*.log*", ]) self.add_cmd_output( [f"ceph {cmd}" for cmd in ceph_mgr_cmds]) # get ceph_cmds again as json for easier automation parsing self.add_cmd_output( [f"ceph {cmd} --format json-pretty" for cmd in ceph_mgr_cmds], subdir="json_output", ) self.add_cmd_output([ f"ceph daemon {m} {cmd}" for m in self.get_socks(directory) for cmd in cmds] ) def get_socks(self, directory): """ Find any available admin sockets under /var/run/ceph (or subdirs for later versions of Ceph) which can be used for ceph daemon commands """ ceph_sockets = [] for rdir, _, files in os.walk(directory): for file in files: if file.startswith('ceph-mgr') and file.endswith('.asok'): ceph_sockets.append(self.path_join(rdir, file)) return ceph_sockets # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/juju.py0000664000175000017500000000471414660147624016124 0ustar arifarif# Copyright (C) 2013 Adam Stokes # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class Juju(Plugin, UbuntuPlugin): short_desc = 'Juju orchestration tool' plugin_name = 'juju' profiles = ('virt', 'sysmgmt') # Using files instead of packages here because there is no identifying # package on a juju machine. files = ('/var/log/juju',) def setup(self): # Juju service names are not consistent through deployments, # so we need to use a wildcard to get the correct service names. for service in self.get_service_names("juju*"): self.add_journal(service) self.add_service_status(service) # Get agent configs for each agent. self.add_copy_spec("/var/lib/juju/agents/*/agent.conf") # Get a directory listing of /var/log/juju and /var/lib/juju self.add_dir_listing([ '/var/log/juju*', '/var/lib/juju*' ], recursive=True) if self.get_option("all_logs"): # /var/lib/juju used to be in the default capture moving here # because it usually was way to big. However, in most cases you # want all logs you want this too. self.add_copy_spec([ "/var/log/juju", "/var/lib/juju", "/var/lib/juju/**/.*", ]) self.add_forbidden_path("/var/lib/juju/kvm") else: # We need this because we want to collect to the limit of all # logs in the directory. self.add_copy_spec("/var/log/juju/*.log") def postproc(self): agents_path = "/var/lib/juju/agents/*" protect_keys = [ "sharedsecret", "apipassword", "oldpassword", "statepassword", ] # Redact simple yaml style "key: value". keys_regex = fr"(^\s*({'|'.join(protect_keys)})\s*:\s*)(.*)" sub_regex = r"\1*********" self.do_path_regex_sub(agents_path, keys_regex, sub_regex) # Redact certificates self.do_file_private_sub(agents_path) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openshift.py0000664000175000017500000004156514660147624017153 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from fnmatch import translate import os import re from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Openshift(Plugin, RedHatPlugin): """This is the plugin for OCP 4.x collections. While this product is still built ontop of kubernetes, there is enough difference in the collection requirements and approach to warrant a separate plugin as opposed to further extending the kubernetes plugin (or the OCP 3.x extensions included in the Red Hat version of the kube plugin). This plugin may collect OCP API information when the `with-api` option is enabled. This option is disabled by default. When enabled, this plugin will collect cluster information and inspect the default namespaces/projects that are created during deployment - i.e. the namespaces of the cluster projects matching openshift.* and kube.*. At the time of this plugin's creation that number of default projects is already north of 50; hence this plugin is expected to take a long time in both the setup() and collect() phases. End-user projects may also be collected from when those projects are included in the `add-namespaces` or `only-namespaces` options. It is expected to need to perform an `oc login` command in order for this plugin to be able to correctly capture information, as system root is not considered cluster root on the cluster nodes in order to access the API. Users will need to either: 1) Accept the use of a well-known stock kubeconfig file provided via a static pod resource for the kube-apiserver 2) Provide the bearer token via the `-k openshift.token` option 3) Provide the bearer token via the `SOSOCPTOKEN` environment variable 4) Otherwise ensure that the root user can successfully run `oc` and get proper output prior to running this plugin It is highly suggested that option #1 be used first, as this uses well known configurations and requires the least information from the user. If using a token, it is recommended to use option #3 as this will prevent the token from being recorded in output saved to the archive. Option #2 may be used if this is considered an acceptable risk. It is not recommended to rely on option #4, though it will provide the functionality needed. """ short_desc = 'Openshift Container Platform 4.x' plugin_name = "openshift" plugin_timeout = 900 profiles = ('openshift',) packages = ('openshift-hyperkube',) master_localhost_kubeconfig = ( '/etc/kubernetes/static-pod-resources/' 'kube-apiserver-certs/secrets/node-kubeconfigs/localhost.kubeconfig' ) oc_cmd = "oc get " option_list = [ PluginOpt('token', default=None, val_type=str, desc='admin token to allow API queries'), PluginOpt('kubeconfig', default=None, val_type=str, desc='Path to a locally available kubeconfig file'), PluginOpt('host', default='https://localhost:6443', desc='host address to use for oc login, including port'), PluginOpt('with-api', default=False, desc='collect output from the OCP API'), PluginOpt('podlogs', default=True, desc='collect logs from each pod'), PluginOpt('podlogs-filter', default='', val_type=str, desc='only collect logs from pods matching this pattern'), PluginOpt('only-namespaces', default='', val_type=str, desc='colon-delimited list of namespaces to collect from'), PluginOpt('add-namespaces', default='', val_type=str, desc=('colon-delimited list of namespaces to add to the ' 'default collection list')) ] def _check_oc_function(self): """Check to see if we can run `oc` commands""" return self.exec_cmd('oc whoami')['status'] == 0 def _check_localhost_kubeconfig(self): """Check if the localhost.kubeconfig exists with system:admin user""" return self.path_exists(self.get_option('kubeconfig')) def _check_oc_logged_in(self): """See if we're logged in to the API service, and if not attempt to do so using provided plugin options """ if self._check_oc_function(): return True if self.get_option('kubeconfig') is None: # If admin doesn't add the kubeconfig # use default localhost.kubeconfig self.set_option( 'kubeconfig', self.master_localhost_kubeconfig ) # Check first if we can use the localhost.kubeconfig before # using token. We don't want to use 'host' option due we use # cluster url from kubeconfig. Default is localhost. if self._check_localhost_kubeconfig(): self.set_default_cmd_environment({ 'KUBECONFIG': self.get_option('kubeconfig') }) oc_res = self.exec_cmd( "oc login -u system:admin " "--insecure-skip-tls-verify=True" ) if oc_res['status'] == 0 and self._check_oc_function(): return True self._log_warn( "The login command failed with status: " f"{oc_res['status']} and error: {oc_res['output']}" ) return False # If kubeconfig is not defined, check if token is provided. token = self.get_option('token') or os.getenv('SOSOCPTOKEN', None) if token: oc_res = self.exec_cmd(f"oc login {self.get_option('host')} " f"--token={token} " "--insecure-skip-tls-verify=True") if oc_res['status'] == 0: if self._check_oc_function(): return True self._log_warn("Attempt to login to OCP API failed, will not run " "or collect `oc` commands") return False self._log_warn("Not logged in to OCP API, and no login token provided." " Will not collect `oc` commands") return False def _setup_namespace_regexes(self): """Combine a set of regexes for collection with any namespaces passed to sos via the -k openshift.add-namespaces option. Note that this does allow for end users to specify namespace regexes of their own. """ if self.get_option('only-namespaces'): return list(self.get_option('only-namespaces').split(':')) collect_regexes = [ 'openshift.*', 'kube.*' ] if self.get_option('add-namespaces'): for nsp in self.get_option('add-namespaces').split(':'): collect_regexes.append(nsp) return collect_regexes def _reduce_namespace_list(self, nsps): """Reduce the namespace listing returned to just the ones we want to collect from. By default, as requested by OCP support personnel, this must include all 'openshift' prefixed namespaces :param nsps list: Namespace names from oc output """ def _match_namespace(namespace, regexes): """Match a particular namespace for inclusion (or not) in the collection phases :param namespace str: The name of a namespace """ for regex in regexes: if re.match(regex, namespace): return True return False regexes = self._setup_namespace_regexes() return list({n for n in nsps if _match_namespace(n, regexes)}) def setup(self): """The setup() phase of this plugin will iterate through all default projects (namespaces), and/or those specified via the `add-namespaces` and `only-namespaces` plugin options. Both of these options accept shell-style regexes. Cluster-wide information, that is information that is not tied to a specific namespace, will be saved in the top-level plugin directory. Each namespace will have it's own subdir within the `namespaces` subdir to aide in organization. From there, each namespace subdir will have a subsequent subdir for each type of API resource the plugin collects. In contrast with the `kubernetes` plugin, this plugin will collect logs from all pods within each namespace, as well as the previous pod's logs, by default. The `-k openshift.podlogs-filter` option can be used to greatly reduce the amount of collected information. """ # Capture the kubelet journal, but don't use it as a service which # would simultaneously enable this and the kubernetes plugin self.add_journal('kubelet') self.add_service_status('kubelet') self.add_forbidden_path([ '/etc/kubernetes/*.crt', '/etc/kubernetes/*.key', ]) self.add_copy_spec('/etc/kubernetes/*') # see if we run `oc` commands if self.get_option('with-api'): can_run_oc = self._check_oc_logged_in() else: can_run_oc = False if can_run_oc: # with an out-of-the-box install, setup time alone has been known # to take over 5 minutes. Print a notification message so that # users don't prematurely think sos has hung during setup self._log_warn( 'Note that the Openshift Container Platform plugin can be ' 'expected in most configurations to take 5+ minutes in both ' 'the setup and collection phases' ) oc_nsps = [] # get 'global' or cluster-level information self.add_cmd_output([ 'oc cluster-info', 'oc get -A pv', 'oc get -A csr', 'oc status', 'oc version' ]) # get non-namespaces api resources self.collect_cluster_resources() # get all namespaces, as data collection will be organized by that _nm_res = self.collect_cmd_output(f"{self.oc_cmd} namespaces") if _nm_res['status'] == 0: nsps = [ n.split()[0] for n in _nm_res['output'].splitlines()[1:] ] oc_nsps = self._reduce_namespace_list(nsps) # collect each namespace individually for namespace in oc_nsps: self.collect_from_namespace(namespace) def collect_cluster_resources(self): """Collect cluster-level (non-namespaced) resources from the API """ global_resources = [ 'clusternetworks', 'clusteroperators', 'clusterversions', 'componentstatuses', 'configs', 'containerruntimeconfigs', 'controllerconfigs', 'dnses', 'hostsubnets', 'infrastructures', 'machineconfigpools', 'machineconfigs', 'netnamespaces', 'networks', 'nodes', 'proxies', 'storageclasses' ] for resource in global_resources: _subdir = f"cluster_resources/{resource}" _tag = [f"ocp_{resource}"] _res = self.collect_cmd_output(f"{self.oc_cmd} {resource}", subdir=_subdir, tags=_tag) if _res['status'] == 0: for _res_name in _res['output'].splitlines()[1:]: self.add_cmd_output( f"oc describe {resource} {_res_name.split()[0]}", subdir=_subdir ) def collect_from_namespace(self, namespace): """Run through the collection routines for an individual namespace. This collection should include all requested resources that exist within that namesapce :param namespace str: The name of the namespace """ # define the list of resources to collect resources = [ 'buildconfigs', 'builds', 'catalogsourceconfigs', 'catalogsources', 'clusterserviceversions', 'configmaps', 'daemonsets', 'deploymentconfigs', 'deployments', 'events', 'horizontalpodautoscalers', 'imagestreams', 'ingresscontrollers', 'ingresses', 'installplans', 'limitranges', 'machines', 'machinesets', 'mcoconfigs', 'net-attach-def', 'operatorgroups', 'operatorsources', 'pods', 'pvc', 'resourcequotas', 'routes', 'secrets', 'services', 'statefulsets', 'subscriptions' ] # save to namespace-specific subdirs to keep the plugin dir organized subdir = f"namespaces/{namespace}" # namespace-specific non-resource collections self.add_cmd_output(f"oc describe namespace {namespace}", subdir=subdir) for res in resources: _subdir = f"{subdir}/{res}" _tags = [ f"ocp_{res}", f"ocp_{namespace}_{res}", namespace ] _get_cmd = f"{self.oc_cmd} --namespace={namespace} {res}" # get the 'normal' output first _res_out = self.collect_cmd_output( _get_cmd, subdir=_subdir, tags=_tags ) # then get specific detail on each instance of the resource if _res_out['status'] == 0: _instances = _res_out['output'].splitlines()[1:] for _instance in _instances: _instance_name = _instance.split()[0] self.add_cmd_output( f"{_get_cmd} {_instance_name} -o yaml", subdir=_subdir, suggest_filename=f"{_instance_name}.yaml" ) # check for podlogs here as a slight optimization to re-running # 'oc get pods' on all namespaces if res == 'pods' and _instances and self.get_option('podlogs'): pod_list = [p.split()[0] for p in _instances] self.collect_podlogs(namespace, pod_list) def collect_podlogs(self, namespace, pod_list): """For any namespace that has active pods in it, collect the current and previous pod's logs :param pod_list list: A list of pod names """ _log_dir = f"namespaces/{namespace}/pods/podlogs" if self.get_option('podlogs-filter'): # this allows shell-style regex which is more commonly known by # sysadmins than python-style regex regex = translate(self.get_option('podlogs-filter')) else: regex = None for pod in pod_list: if regex and not re.match(regex, pod): continue _log_cmd = f"oc logs --namespace={namespace} {pod}" self.add_cmd_output([ _log_cmd, _log_cmd + " -p" ], subdir=_log_dir) def postproc(self): # clear any certificate output self.do_cmd_private_sub('oc ') self.do_file_private_sub('/etc/kubernetes/*') # clear the certificate data from /etc/kubernetes that does not have # the certificate banners that the _private_sub() methods look for _fields = [ '.*.crt', 'client-certificate-data', 'client-key-data', 'certificate-authority-data', '.*.key', 'token', '.*token.*.value' # don't blind match `.*token.*` and lose names ] regex = fr'(\s*({"|".join(_fields)}):)(.*)' self.do_path_regex_sub('/etc/kubernetes/*', regex, r'\1 *******') # scrub secret content self.do_cmd_output_sub('secrets', regex, r'\1 *******') # `oc describe` output can include url-encoded file content. For the # most part this is not important as the majority of these instances # are the contents of bash scripts. However, a select few can contain # actual data, so just scrub everything that matches the describe # format for this content regex = r'(?P(.*\\n)?Source:\s(.*),)((.*?))\n' self.do_cmd_output_sub('oc describe', regex, r'\g *******\n') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/aide.py0000664000175000017500000000263714660147624016053 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Aide(Plugin): short_desc = 'Advanced Intrusion Detection Environment' plugin_name = "aide" profiles = ('system', 'security') packages = ('aide',) conf_file = "/etc/aide/aide.conf" def setup(self): self.add_cmd_output(f"aide -c {self.conf_file} --config-check") if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/aide/", ]) else: self.add_copy_spec([ "/var/log/aide/aide.log" ]) class RedHatAide(Aide, RedHatPlugin): conf_file = "/etc/aide.conf" def setup(self): super().setup() self.add_copy_spec([ "/etc/aide.conf", "/etc/logrotate.d/aide" ]) class DebianAide(Aide, DebianPlugin, UbuntuPlugin): conf_file = "/etc/aide/aide.conf" def setup(self): super().setup() self.add_copy_spec([ "/etc/aide/", "/etc/default/aide" ]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/microk8s.py0000664000175000017500000000375614660147624016713 0ustar arifarif# Copyright (C) 2023 Canonical Ltd., # David Negreira # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class Microk8s(Plugin, UbuntuPlugin): """The Microk8s plugin collects the current status of the microk8s snap on a Ubuntu machine. It will collect logs from journald related to the snap.microk8s units as well as run microk8s commands to retrieve the configuration, status, version and loaded plugins. """ short_desc = 'The lightweight Kubernetes' plugin_name = "microk8s" profiles = ('container',) packages = ('microk8s',) microk8s_cmd = "microk8s" def setup(self): self.add_journal(units="snap.microk8s.*") microk8s_subcmds = [ 'addons repo list', 'config', 'ctr plugins ls', 'ctr plugins ls -d', 'status', 'version' ] self.add_copy_spec( "/var/snap/microk8s/current/credentials/client.config" ) self.add_cmd_output([ f"microk8s {subcmd}" for subcmd in microk8s_subcmds ]) def postproc(self): rsub = r'(certificate-authority-data:|token:)\s.*' self.do_cmd_output_sub("microk8s", rsub, r'\1 "**********"') protect_keys = [ "certificate-authority-data", "client-certificate-data", "client-key-data", ] key_regex = fr'(^\s*({"|".join(protect_keys)})\s*:\s*)(.*)' self.do_path_regex_sub( "/var/snap/microk8s/current/credentials/client.config", key_regex, r"\1*********" ) # vim: set et ts=4 sw=4 sos-4.8.0/sos/report/plugins/logrotate.py0000664000175000017500000000236614660147624017150 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class LogRotate(Plugin, IndependentPlugin): short_desc = 'LogRotate service' plugin_name = 'logrotate' profiles = ('system',) var_puppet_gen = "/var/lib/config-data/puppet-generated/crond" var_ansible_gen = "/var/lib/config-data/ansible-generated/crond" def setup(self): self.add_cmd_output("logrotate --debug /etc/logrotate.conf", suggest_filename="logrotate_debug") self.add_copy_spec([ "/etc/logrotate*", "/var/lib/logrotate.status", "/var/lib/logrotate/logrotate.status", self.var_puppet_gen + "/etc/logrotate-crond.conf", self.var_puppet_gen + "/var/spool/cron/root", self.var_ansible_gen + "/etc/logrotate-crond.conf", self.var_ansible_gen + "/var/spool/cron/root" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cobbler.py0000664000175000017500000000244614660147624016557 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Cobbler(Plugin): plugin_name = "cobbler" short_desc = 'Cobbler installation server' class RedHatCobbler(Cobbler, RedHatPlugin): packages = ('cobbler',) profiles = ('cluster', 'sysmgmt') def setup(self): self.add_copy_spec([ "/etc/cobbler", "/var/log/cobbler", "/var/lib/rhn/kickstarts", "/var/lib/cobbler" ]) self.add_file_tags({ "/etc/clobber/modules.conf": "cobbler_modules_conf", "/etc/cobbler/settings": "cobbler_settings" }) class DebianCobbler(Cobbler, DebianPlugin, UbuntuPlugin): packages = ('cobbler',) def setup(self): self.add_copy_spec([ "/etc/cobbler", "/var/log/cobbler", "/var/lib/cobbler" ]) self.add_forbidden_path("/var/lib/cobbler/isos") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/postfix.py0000664000175000017500000001051114660147624016633 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Postfix(Plugin): short_desc = 'Postfix smtp server' plugin_name = "postfix" profiles = ('mail', 'services') packages = ('postfix',) def forbidden_ssl_keys_files(self): """ list of attributes defining a location of a SSL key file we must forbid from collection """ forbid_attributes = [ "lmtp_tls_dkey_file", "lmtp_tls_eckey_file", "lmtp_tls_key_file", "smtp_tls_dkey_file", "smtp_tls_eckey_file", "smtp_tls_key_file", "smtpd_tls_dkey_file", "smtpd_tls_eckey_file", "smtpd_tls_key_file", "tls_legacy_public_key_fingerprints", "tlsproxy_tls_dkey_file", "tlsproxy_tls_eckey_file", "tlsproxy_tls_key_file", "smtpd_tls_dh1024_param_file", "smtpd_tls_dh512_param_file", "tlsproxy_tls_dh1024_param_file", "tlsproxy_tls_dh512_param_file", ] fpaths = [] try: with open(self.path_join('/etc/postfix/main.cf'), 'r', encoding='UTF-8') as cffile: for line in cffile.readlines(): # ignore comments and take the first word after '=' if line.startswith('#'): continue words = line.split('=') if words[0].strip() in forbid_attributes: fpaths.append(words[1].split()[0]) except Exception: # pylint: disable=broad-except pass return fpaths def forbidden_password_files(self): """ Get the list of password to exclude """ forbid_attributes = ( "lmtp_sasl_password_maps", "smtp_sasl_password_maps", "postscreen_dnsbl_reply_map", "smtp_sasl_auth_cache_name", ) fpaths = [] prefix = 'hash:' option_format = re.compile(r"^(.*)=(.*)") try: with open(self.path_join('/etc/postfix/main.cf'), 'r', encoding='UTF-8') as cffile: for line in cffile.readlines(): # ignore comment and check option format line = re.sub('#.*', '', line) option = option_format.match(line) if option is None: continue # sieving attribute = option.group(1).strip() if attribute in forbid_attributes: filepath = option.group(2).strip() # ignore no filepath if len(filepath) == 0: continue # remove prefix if filepath.startswith(prefix): filepath = filepath[len(prefix):] fpaths.append(filepath) except Exception as err: # pylint: disable=broad-except # error log msg = f"Error parsing main.cf: {err.args[0]}" self._log_error(msg) return fpaths def setup(self): self.add_copy_spec([ "/etc/postfix/", ]) self.add_cmd_output([ 'postconf', 'mailq' ]) # don't collect SSL keys or certs or ssl dir self.add_forbidden_path([ "/etc/postfix/*.key", "/etc/postfix/*.crt", "/etc/postfix/ssl/", ]) self.add_forbidden_path(self.forbidden_ssl_keys_files()) self.add_forbidden_path(self.forbidden_password_files()) class RedHatPostfix(Postfix, RedHatPlugin): files = ('/etc/rc.d/init.d/postfix',) packages = ('postfix',) def setup(self): super().setup() self.add_copy_spec("/etc/mail") class DebianPostfix(Postfix, DebianPlugin, UbuntuPlugin): packages = ('postfix',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/aap_hub.py0000664000175000017500000000247314660147624016546 0ustar arifarif# Copyright (c) 2024 Mike Silmser # Copyright (c) 2024 Lucas Benedito # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class AAPAutomationHub(Plugin, RedHatPlugin): short_desc = 'AAP Automation Hub plugin' plugin_name = 'aap_hub' profiles = ('sysmgmt', 'ansible',) packages = ('automation-hub',) def setup(self): self.add_copy_spec([ "/etc/ansible-automation-platform/", "/var/log/ansible-automation-platform/hub/worker.log*", "/var/log/ansible-automation-platform/hub/pulpcore-api.log*", "/var/log/ansible-automation-platform/hub/pulpcore-content.log*", "/var/log/nginx/automationhub.access.log*", "/var/log/nginx/automationhub.error.log*", ]) self.add_dir_listing([ "/etc/ansible-automation-platform/", "/var/log/ansible-automation-platform/", ], recursive=True) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/stratis.py0000664000175000017500000000217514660147624016637 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Stratis(Plugin, RedHatPlugin): short_desc = 'Stratis Storage' plugin_name = 'stratis' packages = ('stratis-cli', 'stratisd') services = ('stratisd',) profiles = ('storage',) def setup(self): subcmds = [ 'pool list', 'pool list --stopped', 'filesystem list', 'blockdev list', 'key list', 'daemon version', 'report engine_state_report', 'report managed_objects_report', 'report stopped_pools', '--version', ] self.add_cmd_output([f"stratis {subcmd}" for subcmd in subcmds]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/manageiq.py0000664000175000017500000000555514660147624016735 0ustar arifarif# -*- python -*- # -*- coding: utf-8 -*- # Copyright (C) 2015 Red Hat, Inc., Pep Turró Mauri # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from os import environ import os.path from sos.report.plugins import Plugin, RedHatPlugin class ManageIQ(Plugin, RedHatPlugin): short_desc = 'ManageIQ/CloudForms related information' plugin_name = 'manageiq' miq_dir = '/var/www/miq/vmdb' packages = ( 'cfme', 'cfme-appliance', 'cfme-gemset', 'cfme-appliance-tools', 'cfme-appliance-common' ) files = ( os.path.join(miq_dir, 'BUILD'), os.path.join(miq_dir, 'GUID'), os.path.join(miq_dir, 'VERSION'), os.path.join(miq_dir, 'REGION') ) # Config files to collect from miq_dir/config/ miq_conf_dir = os.path.join(miq_dir, "config") miq_conf_files = [ '*.rb', '*.yaml', '*.yml', '*.yml.db', '*.yml.sample', 'settings/*.yml', 'environments/*.rb', 'environments/*.yml', 'environments/patches/*.rb', 'initializers/*.rb', 'database.yml.old', 'brakeman.ignore', ] # Log files to collect from miq_dir/log/ miq_log_dir = os.path.join(miq_dir, "log") miq_main_logs = [ 'ansible_tower.log', 'top_output.log', 'evm.log', 'production.log', 'automation.log', ] miq_log_files = [ '*.log', 'apache/*.log', '*.txt', '*.yml', ] def setup(self): if self.get_option("all_logs"): # turn all log files to a glob to include logrotated ones self.miq_log_files = map(lambda x: x + '*', self.miq_log_files) self.add_copy_spec(list(self.files)) self.add_copy_spec([ self.path_join(self.miq_conf_dir, x) for x in self.miq_conf_files ]) # Collect main log files without size limit. self.add_copy_spec([ self.path_join(self.miq_log_dir, x) for x in self.miq_main_logs ], sizelimit=0) self.add_copy_spec([ self.path_join(self.miq_log_dir, x) for x in self.miq_log_files ]) self.add_copy_spec([ "/var/log/tower.log", "/etc/manageiq/postgresql.conf.d/*.conf" ]) if environ.get("APPLIANCE_PG_DATA"): pg_dir = environ.get("APPLIANCE_PG_DATA") self.add_copy_spec([ self.path_join(pg_dir, 'pg_log'), self.path_join(pg_dir, 'postgresql.conf') ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/pmem.py0000664000175000017500000000654114660147624016105 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class PMem(Plugin, IndependentPlugin): """This plugin collects data from Persistent Memory devices, commonly referred to as NVDIMM's or Storage Class Memory (SCM) """ short_desc = 'Persistent Memory Devices' plugin_name = 'pmem' profiles = ('storage', 'hardware', 'memory') # Utilities can be installed by package or self compiled packages = ('ndctl', 'daxctl', 'ipmctl') commands = ('ndctl', 'daxctl', 'ipmctl') def setup(self): # Copy the contents of the /etc/ndctl & /var/log/ipmctl # directories and /etc/ipmctl.conf file self.add_copy_spec([ "/etc/ndctl", "/etc/ipmctl.conf", "/var/log/ipmctl" ]) # Use the ndctl-list(1) command to collect: # -i Include idle (not enabled) devices in the listing # -vvv Increase verbosity of the output # -B Include bus info in the listing # -D Include dimm info in the listing # -F Include dimm firmware info in the listing # -H Include dimm health info in the listing # -M Include media errors (badblocks) in the listing # -N Include namespace info in the listing # -R Include region info in the listing # -X Include device-dax info in the listing # # Output is JSON formatted self.add_cmd_output([ "ndctl --version", "ndctl list -vvv", "ndctl list -iBDFHMNRX", "ndctl read-labels -j all" ]) # Use the daxctl-list(1) command to collect: # -i Include idle (not enabled / zero-sized) devices in the listing # -D Include device-dax instance info in the listing # -R Include region info in the listing # # Output is JSON formatted self.add_cmd_output([ "daxctl list", "daxctl list -iDR" ]) # Use the ipmctl(1) command to collect data from # Intel(R) Optane(TM) Persistent Memory Modules. self.add_cmd_output([ "ipmctl version", "ipmctl show -cap", "ipmctl show -cel", "ipmctl show -dimm", "ipmctl show -a -dimm", "ipmctl show -dimm -pcd", "ipmctl show -dimm -performance", "ipmctl show -error Thermal -dimm", "ipmctl show -error Media -dimm", "ipmctl show -firmware", "ipmctl show -goal", "ipmctl show -memoryresources", "ipmctl show -performance", "ipmctl show -preferences", "ipmctl show -region", "ipmctl show -sensor", "ipmctl show -a -sensor", "ipmctl show -socket", "ipmctl show -system", "ipmctl show -system -capabilities", "ipmctl show -a -system -capabilities", "ipmctl show -topology", "ipmctl show -a -topology" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/elastic.py0000664000175000017500000000477414660147624016601 0ustar arifarif# Copyright (C) 2018 Amit Ghadge # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin class Elastic(Plugin, IndependentPlugin): short_desc = 'ElasticSearch service' plugin_name = 'elastic' profiles = ('services', ) packages = ('elasticsearch',) def get_hostname_port(self, els_config_file): """ Get hostname and port number """ hostname = "localhost" port = "9200" try: with open(els_config_file, encoding='UTF-8') as fread: for line in fread: network_host = re.search(r'(^network.host):(.*)', line) network_port = re.search(r'(^http.port):(.*)', line) if network_host and len(network_host.groups()) == 2: hostname = network_host.groups()[-1].strip() hostname = re.sub(r'"|\'', '', hostname) continue if network_port and len(network_port.groups()) == 2: port = network_port.groups()[-1].strip() except Exception as err: # pylint: disable=broad-except self._log_info(f"Failed to parse {els_config_file}: {err}") return hostname, port def setup(self): els_config_file = self.path_join( "/etc/elasticsearch/elasticsearch.yml" ) self.add_copy_spec(els_config_file) if self.get_option("all_logs"): self.add_copy_spec("/var/log/elasticsearch/*") else: self.add_copy_spec("/var/log/elasticsearch/*.log") host, port = self.get_hostname_port(els_config_file) endpoint = host + ":" + port self.add_cmd_output([ f"curl -X GET '{endpoint}/_cluster/settings?pretty'", f"curl -X GET '{endpoint}/_cluster/health?pretty'", f"curl -X GET '{endpoint}/_cluster/stats?pretty'", f"curl -X GET '{endpoint}/_cat/nodes?v'", f"curl -X GET '{endpoint}/_cat/indices'", f"curl -X GET '{endpoint}/_cat/shards'", f"curl -X GET '{endpoint}/_cat/aliases'", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/opengl.py0000664000175000017500000000126614660147624016432 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OpenGL(Plugin, IndependentPlugin): short_desc = 'OpenGL' plugin_name = 'opengl' profiles = ('hardware', 'desktop', 'gpu') files = ('/usr/bin/glxinfo',) def setup(self): self.add_cmd_output([ "glxinfo", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/udisks.py0000664000175000017500000000144414660147624016446 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Udisks(Plugin, IndependentPlugin): short_desc = 'udisks disk manager' plugin_name = 'udisks' profiles = ('system', 'hardware') commands = ('udisksctl',) def setup(self): self.add_copy_spec([ "/etc/udisks2/", ]) self.add_cmd_output([ "udisksctl status", "udisksctl dump", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/anaconda.py0000664000175000017500000000256614660147624016716 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Anaconda(Plugin, RedHatPlugin): short_desc = 'Anaconda installer' plugin_name = 'anaconda' profiles = ('system',) packages = ('anaconda',) files = ( '/var/log/anaconda.log', '/var/log/anaconda' ) def setup(self): paths = [ "/root/anaconda-ks.cfg" ] if self.path_isdir('/var/log/anaconda'): # new anaconda paths.append('/var/log/anaconda') else: paths = paths + [ "/var/log/anaconda.*", "/root/install.log", "/root/install.log.syslog" ] self.add_copy_spec(paths) def postproc(self): self.do_file_sub( "/root/anaconda-ks.cfg", r"(\s*rootpw\s*).*", r"\1********" ) self.do_file_sub( "/root/anaconda-ks.cfg", r"(user.*--password=*\s*)\s*(\S*)", r"\1********" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/frr.py0000664000175000017500000000345114660147624015735 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Frr(Plugin, RedHatPlugin): """ FRR is a routing project that provides numerous traditional routing protocols for Linux platforms. In particular, OpenStack uses FRR to provide BGP functionality for the overcloud nodes. This plugin is primarily designed the deployment of FRR within OSP environments, which deploy FRR in a container. """ short_desc = 'Frr routing service' plugin_name = 'frr' profiles = ('network',) files = ('/etc/frr/zebra.conf',) packages = ('frr',) containers = ('frr',) def setup(self): var_ansible_gen = "/var/lib/config-data/ansible-generated/frr" self.add_copy_spec([ "/etc/frr/", var_ansible_gen + "/etc/frr/", ]) if self.container_exists('frr'): subcmds = [ 'show bgp detail', 'show bgp neighbors', 'show bgp summary', 'show history', 'show ip bgp detail', 'show ip bgp neighbors', 'show ip bgp summary', 'show ip bgp', 'show ip route', 'show ipv6 route', 'show running-config', 'show version', ] self.add_cmd_output( [f"vtysh -c '{subcmd}'" for subcmd in subcmds], container='frr' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/iprconfig.py0000664000175000017500000001103414660147624017120 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # This plugin enables collection of logs for Power systems import re from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate class IprConfig(Plugin, IndependentPlugin): short_desc = 'IBM Power RAID storage adapter configuration information' plugin_name = 'iprconfig' packages = ('iprutils',) architectures = ('ppc64.*',) def setup(self): show_ioas = self.collect_cmd_output( "iprconfig -c show-ioas", pred=SoSPredicate(self, kmods=['sg']) ) if not show_ioas['status'] == 0: return self.add_cmd_output([ "iprconfig -c show-config", "iprconfig -c show-alt-config", "iprconfig -c show-arrays", "iprconfig -c show-jbod-disks", "iprconfig -c show-ioas", "iprconfig -c show-hot-spares", "iprconfig -c show-af-disks", "iprconfig -c show-all-af-disks", "iprconfig -c show-slots", "iprconfig -c dump" ]) devices = [] if show_ioas['output']: dev = re.compile('sg') for line in show_ioas['output'].splitlines(): temp = line.split(' ') # temp[0] holds the device name if dev.search(temp[0]): devices.append(temp[0]) for device in devices: self.add_cmd_output(f"iprconfig -c show-details {device}") self.add_cmd_output(f"iprconfig -c show-battery-info {device}") self.add_cmd_output(f"iprconfig -c show-perf {device}") # Look for IBM Power RAID enclosures (iprconfig lists them) show_config = self.collect_cmd_output("iprconfig -c show-config") if not show_config['status'] == 0: return if not show_config['output']: return # iprconfig -c show-config # Name PCI/SCSI Location Description Status # ------ ------------------------- ------------------------- ----------------- # 0005:60:00.0/0: PCI-E SAS RAID Adapter Operational # sda 0005:60:00.0/0:0:0:0 Physical Disk Active # sdb 0005:60:00.0/0:1:0:0 Physical Disk Active # sdc 0005:60:00.0/0:2:0:0 Physical Disk Active # sdd 0005:60:00.0/0:3:0:0 Physical Disk Active # sde 0005:60:00.0/0:4:0:0 Physical Disk Active # sdf 0005:60:00.0/0:5:0:0 Physical Disk Active # 0005:60:00.0/0:8:0:0 Enclosure Active # 0005:60:00.0/0:8:1:0 Enclosure Active show_alt_config = "iprconfig -c show-alt-config" altconfig = self.collect_cmd_output(show_alt_config) if (altconfig['status'] != 0) or not altconfig['output']: return # iprconfig -c show-alt-config # Name Resource Path/Address Vendor Product ID Status # ------ -------------------------- -------- ---------------- ----------------- # sg9 0: IBM 57C7001SISIOA Operational # sg0 0:0:0:0 IBM MBF2300RC Active # sg1 0:1:0:0 IBM MBF2300RC Active # sg2 0:2:0:0 IBM HUC106030CSS600 Active # sg3 0:3:0:0 IBM HUC106030CSS600 Active # sg4 0:4:0:0 IBM HUC106030CSS600 Active # sg5 0:5:0:0 IBM HUC106030CSS600 Active # sg7 0:8:0:0 IBM VSBPD6E4A 3GSAS Active # sg8 0:8:1:0 IBM VSBPD6E4B 3GSAS Active for line in show_config['output'].splitlines(): if "Enclosure" in line: temp = re.split(r'\s+', line) # temp[1] holds the PCI/SCSI location _, scsi = temp[1].split('/') for alt_line in altconfig['output'].splitlines(): if scsi in alt_line: temp = alt_line.split(' ') # temp[0] holds device name self.add_cmd_output("iprconfig -c " f"query-ses-mode {temp[0]}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/fail2ban.py0000664000175000017500000000147614660147624016627 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Fail2Ban(Plugin, IndependentPlugin): short_desc = "Fail2Ban daemon" plugin_name = "fail2ban" profiles = ('security',) packages = ('fail2ban', 'fail2ban-server') servicess = ('fail2ban',) def setup(self): self.add_copy_spec([ '/etc/fail2ban', ]) self.add_cmd_output([ 'fail2ban-client status', 'fail2ban-client banned', ]) sos-4.8.0/sos/report/plugins/fcoe.py0000664000175000017500000000177214660147624016064 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class FCoE(Plugin, RedHatPlugin): short_desc = 'Fibre Channel over Ethernet' plugin_name = 'fcoe' profiles = ('storage', 'hardware') packages = ('fcoe-utils',) def setup(self): # Here we capture the information about all # FCoE instances with the -i option, and # information about all discovered FCFs # with the -f option self.add_cmd_output([ "fcoeadm -i", "fcoeadm -f" ]) # Here we grab information about the # interfaces's config files self.add_copy_spec("/etc/fcoe") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/snap.py0000664000175000017500000000576714660147624016121 0ustar arifarif# Copyright (c) 2017 Bryan Quigley # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin class Snap(Plugin, IndependentPlugin): short_desc = 'Snap packages' plugin_name = 'snap' profiles = ('system', 'sysmgmt', 'packagemanager') packages = ('snapd',) services = ('snapd',) def setup(self): self.add_copy_spec("/var/lib/snapd/state.json") self.add_cmd_output("snap list --all", root_symlink="installed-snaps") self.add_cmd_output([ "snap --version", "snap version", "snap whoami", "snap model --verbose", "snap model --serial --verbose", "snap services", "snap connections", "snap changes --abs-time", "snap validate", "snap debug state --abs-time --changes /var/lib/snapd/state.json", "snap debug stacktraces", "snap get system -d", ]) all_pkgs = self.policy.package_manager.packages for pkg_name in all_pkgs: pkg = self.policy.package_manager.pkg_by_name(pkg_name) if pkg['pkg_manager'] == 'snap': self.add_cmd_output(f"snap connections {pkg['name']}") self.add_cmd_output("snap debug connectivity", timeout=10) # If we have gadget snaps, then we collect more files, this is # typically defined in the Notes column snap_list = self.exec_cmd('snap list') if snap_list['status'] == 0: output = snap_list['output'] for line in output.splitlines()[1:]: if line == "": continue snap_pkg = line.split() if re.match(r".*gadget.*$", snap_pkg[5]): self.add_copy_spec([ f"/snap/{snap_pkg[0]}/current/meta/gadget.yaml", ]) snap_changes = self.collect_cmd_output('snap changes') if snap_changes['status'] == 0: output = snap_changes['output'] for line in output.splitlines()[1:]: if line == "": continue change = line.split() change_id, change_status = change[0], change[1] if change_status in ("Doing", "Error"): self.add_cmd_output(f"snap tasks {change_id} --abs-time") def postproc(self): self.do_file_sub( "/var/lib/snapd/state.json", (r"\"(macaroon|store-macaroon|key-id|session-macaroon|macaroon-key" r"|store-discharges)\":\"?\[?([\"?A-Za-z0-9_=\-,]*)\"?\]?"), r'"\1":"***"') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sas3ircu.py0000664000175000017500000000246714660147624016706 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class SAS3ircu(Plugin, IndependentPlugin): """ The sas3ircu plugin is intended to gather information for sas adapters, particularly sas-3 RAID adapters, and will collect information on each adapter discovered on the system. """ short_desc = 'SAS-3 Integrated RAID adapter information' plugin_name = "sas3ircu" commands = ("sas3ircu",) def setup(self): # get list of adapters result = self.collect_cmd_output("sas3ircu list", timeout=5) if result["status"] == 0: # only want devices sas_lst = result["output"].splitlines()[10:-1] # for each adapter get some basic info for sas_info in sas_lst: sas_num = sas_info.split()[0] self.add_cmd_output(f"sas3ircu {sas_num} display", timeout=5) self.add_cmd_output(f"sas3ircu {sas_num} status", timeout=5) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/storcli.py0000664000175000017500000000346414660147624016627 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class StorCLI(Plugin, IndependentPlugin): short_desc = 'LSI MegaRAID devices' plugin_name = 'storcli' profiles = ('system', 'storage', 'hardware',) packages = ('storcli',) option_list = [ PluginOpt('json', default=False, desc='collect data in JSON format') ] def setup(self): cmd = '/opt/MegaRAID/storcli/storcli64' subcmds = [ 'show ctrlcount', '/call show AliLog', '/call show all', '/call show termlog', '/call/bbu show all', '/call/cv show all', '/call/dall show', '/call/eall show all', '/call/eall/sall show all', '/call/sall show all', '/call/vall show all', ] logpath = self.get_cmd_output_path() json = ' J' if self.get_option('json') else '' for subcmd in subcmds: self.add_cmd_output( f"{cmd} {subcmd}{json}", suggest_filename=f"storcli64_{subcmd}{json}", runat=logpath) # /call show events need 'file=' option to get adapter info like below # "Adapter: # - Number of Events: xxx". subcmd = '/call show events' self.add_cmd_output( f"{cmd} {subcmd} file=/dev/stdout{json}", suggest_filename=f"storcli64_{subcmd}{json}", runat=logpath) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/grub.py0000664000175000017500000000173414660147624016105 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Grub(Plugin, IndependentPlugin): short_desc = 'GRUB bootloader' plugin_name = 'grub' profiles = ('boot',) packages = ('grub',) def setup(self): self.add_copy_spec([ "/boot/efi/EFI/*/grub.conf", "/boot/grub/grub.conf", "/boot/grub/device.map", "/etc/grub.conf", "/etc/grub.d" ]) def postproc(self): self.do_path_regex_sub( r".*\/grub.conf", r"(password\s*)(--md5\s*|\s*)(.*)", r"\1\2********" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/x11.py0000664000175000017500000000260314660147624015553 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class X11(Plugin, IndependentPlugin): short_desc = 'X windowing system' plugin_name = 'x11' profiles = ('hardware', 'desktop') files = ('/etc/X11',) def setup(self): self.add_copy_spec([ "/etc/X11", "/var/log/Xorg.*.log", "/var/log/Xorg.*.log.old", "/var/log/XFree86.*.log", "/var/log/XFree86.*.log.old", ]) self.add_forbidden_path([ "/etc/X11/X", "/etc/X11/fontpath.d" ]) self.add_cmd_output([ "xrandr --verbose" ]) self.add_env_var([ 'DISPLAY', 'DESKTOP_SESSION', 'XDG_SESSION_TYPE', 'XDG_SESSION_DESKTOP', 'XMODIFIERS', 'XDG_CURRENT_DESKTOP', 'XDG_SEAT', 'XDG_RUNTIME_DIR', 'XAUTHORITY', 'XDG_SESSION_PATH', 'XDG_SEAT_PATH', 'XDG_SESSION_ID' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/memcached.py0000664000175000017500000000311514660147624017047 0ustar arifarif# Copyright (C) 2018 Mikel Olasagasti Uranga # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Memcached(Plugin): short_desc = 'memcached distributed memory caching system' plugin_name = 'memcached' profiles = ('webserver',) packages = ('memcached',) mm_tool = "memcached-tool" def setup(self): host = "127.0.0.1:11211" subcmds = [ "display", "dump", "keys", "stats", "settings", ] self.add_cmd_output([ f"{self.mm_tool} {host} {subcmd}" for subcmd in subcmds]) class RedHatMemcached(Memcached, RedHatPlugin): files = ('/etc/sysconfig/memcached',) def setup(self): super().setup() self.add_copy_spec("/etc/sysconfig/memcached", tags="sysconfig_memcached") class DebianMemcached(Memcached, DebianPlugin, UbuntuPlugin): files = ('/etc/default/memcached',) def setup(self): self.mm_tool = "/usr/share/memcached/scripts/memcached-tool" super().setup() self.add_copy_spec([ "/etc/memcached.conf", "/etc/default/memcached" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/unbound.py0000664000175000017500000000160314660147624016613 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Unbound(Plugin, IndependentPlugin): short_desc = 'Unbound DNS resolver' plugin_name = 'unbound' profiles = ('system', 'services', 'network') packages = ('unbound', 'unbound-libs') def setup(self): self.add_copy_spec([ "/etc/sysconfig/unbound", "/etc/unbound/unbound.conf", "/usr/lib/tmpfiles.d/unbound.conf", "/etc/unbound/conf.d/", "/etc/unbound/local.d/", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/clear_containers.py0000664000175000017500000000512514660147624020457 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin class ClearContainers(Plugin, IndependentPlugin): short_desc = 'Intel(R) Clear Containers configuration' plugin_name = 'clear_containers' profiles = ('system', 'virt', 'container') runtime = 'cc-runtime' packages = (runtime,) services = ('cc-proxy',) loglimit = None def collect_cc_config_files(self): """ Collect Clear Containers config files """ # start with the default file locations config_files = [ '/etc/clear-containers/configuration.toml' '/usr/share/defaults/clear-containers/configuration.toml' ] # obtain a list of config files by asking the runtime cmd = f"{self.runtime} --cc-show-default-config-paths" configs = self.exec_cmd(cmd)['output'] for config in configs.splitlines(): if config != "": config_files.append(config) # get a unique list of config files config_files = set(config_files) self.add_copy_spec(config_files) def collect_cc_log_files(self): """ Collect Clear Containers log files """ # start with the default global log log_files = [ '/var/lib/clear-containers/runtime/runtime.log' ] # query the runtime to find the configured global log file cmd = f"{self.runtime} cc-env" output = self.exec_cmd(cmd)['output'] for line in output.splitlines(): result = re.search(r'\bGlobalLogPath\b\s+=\s+"(.+)"', line) if result: global_logfile = result.group(1) if global_logfile: log_files.append(global_logfile) break # get a unique list of log files log_files = set(log_files) self.add_copy_spec(log_files, self.loglimit) def setup(self): self.loglimit = self.get_option("log_size") if self.get_option("all_logs"): # no loglimit on amount of data recorded self.loglimit = None self.add_cmd_output(f"{self.runtime} cc-env") self.collect_cc_config_files() self.collect_cc_log_files() self.add_journal(identifier="cc-shim") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/udev.py0000664000175000017500000000160414660147624016105 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Udev(Plugin, IndependentPlugin): short_desc = 'udev dynamic device management' plugin_name = 'udev' profiles = ('system', 'hardware', 'boot') def setup(self): self.add_copy_spec([ "/etc/udev/udev.conf", "/lib/udev/rules.d", "/etc/udev/rules.d/*" ]) self.add_file_tags({ "/etc/udev/rules.d/70-persistent-net.rules": "udev_persistent_net_rules" }) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovirt_provider_ovn.py0000664000175000017500000000224014660147624021076 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OvirtProviderOvn(Plugin, RedHatPlugin): short_desc = 'oVirt OVN Provider' packages = ('ovirt-provider-ovn',) plugin_name = 'ovirt_provider_ovn' profiles = ('virt',) provider_conf = '/etc/ovirt-provider-ovn/ovirt-provider-ovn.conf' def setup(self): self.add_copy_spec(self.provider_conf) self.add_copy_spec('/etc/ovirt-provider-ovn/conf.d/*') spec = '/var/log/ovirt-provider-ovn.log' if self.get_option('all_logs'): spec += '*' self.add_copy_spec(spec) def postproc(self): self.do_file_sub(self.provider_conf, r'(ovirt-sso-client-secret\s*=\s*)(.*)', r'\1*************') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kubernetes.py0000664000175000017500000002525114660147624017315 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Neependra Khare # Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from fnmatch import translate import re import json import os from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) KUBE_PACKAGES = ( 'kubelet', 'kubernetes', ) KUBE_SVCS = ( 'kubelet', 'kube-apiserver', 'kube-proxy', 'kube-scheduler', 'kube-controller-manager', ) KUBECONFIGS = ( '/etc/kubernetes/admin.conf', ) class Kubernetes(Plugin): short_desc = 'Kubernetes container orchestration platform' plugin_name = "kubernetes" profiles = ('container',) plugin_timeout = 1200 config_files = [ "/etc/kubernetes", "/run/flannel", ] resources = [ 'events', 'deployments', 'ingresses', 'pods', 'pvc', 'services', 'daemonsets', 'replicasets', 'endpoints', 'statefulsets', 'configmaps', 'serviceaccounts', 'secrets', 'jobs', 'cronjobs', 'clusterroles', 'clusterrolebindings', 'limitranges', 'resourcequotas', ] # these are not namespaced, must pull separately. global_resources = [ 'sc', 'pv', 'roles', 'rolebindings', ] option_list = [ PluginOpt('all', default=False, desc='collect all namespace output separately'), PluginOpt('describe', default=False, desc='collect describe output of all resources'), PluginOpt('podlogs', default=False, desc='capture stdout/stderr logs from pods'), PluginOpt('podlogs-filter', default='', val_type=str, desc='only collect logs from pods matching this pattern') ] kube_cmd = "kubectl" def set_kubeconfig(self): if os.environ.get('KUBECONFIG'): return for _kconf in self.files: if self.path_exists(_kconf): self.kube_cmd += f" --kubeconfig={_kconf}" break def check_is_master(self): """ Check if this is the master node """ return any(self.path_exists(f) for f in self.files) def setup(self): self.add_copy_spec(self.config_files) self.add_env_var([ 'KUBECONFIG', 'KUBERNETES_HTTP_PROXY', 'KUBERNETES_HTTPS_PROXY', 'KUBERNETES_NO_PROXY', ]) # We can only grab kubectl output from the master if not self.check_is_master(): return for subcmd in ['version', 'config view']: self.add_cmd_output( f'{self.kube_cmd} {subcmd}', subdir='cluster-info' ) if self.get_option('all'): self.add_cmd_output([ f"{self.kube_cmd} get -o json {res}" for res in self.global_resources ], subdir='cluster-info') else: self.add_cmd_output([ f"{self.kube_cmd} get {res}" for res in self.global_resources ], subdir='cluster-info') # Get detailed node information nodes = self.collect_cmd_output(f"{self.kube_cmd} get nodes", subdir='cluster-info') if nodes['status'] == 0 and self.get_option('describe'): for line in nodes['output'].splitlines()[1:]: # find first word in the line and ignore empty+blank lines words = line.split() if not words: continue node = words[0] self.add_cmd_output( f"{self.kube_cmd} describe node {node}", subdir='cluster-info' ) self.add_cmd_output([ f"{self.kube_cmd} get -o json nodes", ], subdir='cluster-info') # Also collect master metrics self.add_cmd_output( f"{self.kube_cmd} get --raw /metrics", subdir='cluster-info' ) # CNV is not part of the base installation, but can be added if self.is_installed('kubevirt-virtctl'): self.resources.extend(['vms', 'vmis']) self.add_cmd_output('virtctl version') self.collect_per_resource_details() self.collect_all_resources() def collect_per_resource_details(self): """ Collect details about each resource in all namespaces """ # get all namespaces in use kns = self.collect_cmd_output(f'{self.kube_cmd} get namespaces', subdir='cluster-info') # namespace is the 1st word on line, until the line has spaces only kn_output = kns['output'].splitlines()[1:] knsps = [n.split()[0] for n in kn_output if n and len(n.split())] for nspace in knsps: knsp = f'--namespace={nspace}' if self.get_option('all'): k_cmd = f'{self.kube_cmd} get -o json {knsp}' for res in self.resources: self.add_cmd_output( f'{k_cmd} {res}', subdir=f'cluster-info/{nspace}' ) if self.get_option('describe'): # need to drop json formatting for this k_cmd = f'{self.kube_cmd} {knsp}' for res in self.resources: if res == 'events': continue ret = self.exec_cmd(f'{k_cmd} get {res}') if ret['status'] == 0: k_list = [k.split()[0] for k in ret['output'].splitlines()[1:]] for item in k_list: k_cmd = f'{self.kube_cmd} {knsp}' self.add_cmd_output( f'{k_cmd} describe {res} {item}', subdir=f'cluster-info/{nspace}/{res}' ) if self.get_option('podlogs'): self._get_pod_logs(knsp) def _get_pod_logs(self, namespace): k_cmd = f'{self.kube_cmd} get -o json {namespace}' ret = self.exec_cmd(f'{k_cmd} pods') if ret['status'] == 0: pods = json.loads(ret['output']) # allow shell-style regex reg = (translate(self.get_option('podlogs-filter')) if self.get_option('podlogs-filter') else None) for pod in pods["items"]: if reg and not re.match(reg, pod["metadata"]["name"]): continue _subdir = (f'cluster-info/' f'{pod["metadata"]["namespace"]}/podlogs/' f'{pod["metadata"]["name"]}') if "containers" in pod["spec"]: for cont in pod["spec"]["containers"]: pod_name = pod["metadata"]["name"] cont_name = cont["name"] self.add_cmd_output( f'{self.kube_cmd} {namespace} logs ' f'{pod_name} -c {cont_name}', subdir=_subdir ) if "initContainers" in pod["spec"]: for cont in pod["spec"]["initContainers"]: pod_name = pod["metadata"]["name"] cont_name = cont["name"] self.add_cmd_output( f'{self.kube_cmd} {namespace} logs ' f'{pod_name} -c {cont_name}', subdir=_subdir ) def collect_all_resources(self): """ Collect details about all resources """ if not self.get_option('all'): k_cmd = f'{self.kube_cmd} get --all-namespaces=true' for res in self.resources: self.add_cmd_output( f'{k_cmd} {res}', subdir='cluster-info' ) def postproc(self): # First, clear sensitive data from the json output collected. # This will mask values when the "name" looks susceptible of # values worth obfuscating, i.e. if the name contains strings # like "pass", "pwd", "key" or "token" env_regexp = r'(?P{\s*"name":\s*[^,]*' \ r'(pass|pwd|key|token|cred|PASS|PWD|KEY)[^,]*,\s*"value":)[^}]*' self.do_cmd_output_sub(self.kube_cmd, env_regexp, r'\g "********"') # Next, we need to handle the private keys and certs in some # output that is not hit by the previous iteration. self.do_cmd_private_sub(self.kube_cmd) pathexp = fr'^({"|".join(self.config_files)})' self.do_file_private_sub(pathexp) # clear base64 encoded PEM from kubeconfigs files regexp = r'LS0tLS1CRUdJ[A-Za-z0-9+/=]+' subst = '***** SCRUBBED BASE64 PEM *****' pathexp = fr'^({"|".join(list(self.files)+self.config_files)})' self.do_path_regex_sub(pathexp, regexp, subst) class RedHatKubernetes(Kubernetes, RedHatPlugin): packages = KUBE_PACKAGES files = KUBECONFIGS services = KUBE_SVCS def check_enabled(self): # do not run at the same time as the openshift plugin if self.is_installed("openshift-hyperkube"): return False return super().check_enabled() def setup(self): self.set_kubeconfig() super().setup() class UbuntuKubernetes(Kubernetes, UbuntuPlugin, DebianPlugin): packages = KUBE_PACKAGES files = KUBECONFIGS + ( '/root/cdk/cdk_addons_kubectl_config', '/var/snap/microk8s/current/credentials/client.config', ) services = KUBE_SVCS + ( 'snap.kubelet.daemon', 'snap.kube-apiserver.daemon', 'snap.kube-proxy.daemon', 'snap.kube-scheduler.daemon', 'snap.kube-controller-manager.daemon', # CDK 'cdk.master.auth-webhook', ) def setup(self): self.set_kubeconfig() if self.is_installed('microk8s'): self.kube_cmd = 'microk8s kubectl' self.config_files.extend([ '/root/cdk/kubelet/config.yaml', '/root/cdk/audit/audit-policy.yaml', ]) super().setup() # vim: et ts=5 sw=4 sos-4.8.0/sos/report/plugins/cifs.py0000664000175000017500000000170214660147624016065 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Cifs(Plugin, IndependentPlugin): short_desc = 'SMB file system information' plugin_name = 'cifs' profiles = ('storage', 'network', 'cifs') packages = ('cifs-utils',) def setup(self): self.add_forbidden_path([ "/proc/fs/cifs/traceSMB", "/proc/fs/cifs/cifsFYI", ]) self.add_copy_spec([ "/etc/request-key.d/cifs.spnego.conf", "/etc/request-key.d/cifs.idmap.conf", "/proc/keys", "/proc/fs/cifs/*", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/opencontrail.py0000664000175000017500000000255114660147624017641 0ustar arifarif# Copyright (C) 2021 Mirntis, Inc., Oleksii Molchanov # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OpenContrail(Plugin, IndependentPlugin): short_desc = "OpenContrail SDN" plugin_name = 'opencontrail' profiles = ("network",) packages = ('opencontrail',) containers = ( 'opencontrail.*', 'vrouter.*', ) def setup(self): # assuming the container names will start with "opencontrail" in_container = self.container_exists('opencontrail.*') if in_container: cnames = self.get_containers(get_all=True) cnames = [c[1] for c in cnames if 'opencontrail' in c[1]] for cntr in cnames: self.add_cmd_output('contrail-status', container=cntr) else: self.add_cmd_output("contrail-status") self.add_cmd_output("vif --list") self.add_copy_spec([ "/etc/contrail/*", "/var/log/contrail/*", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cron.py0000664000175000017500000000201614660147624016101 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Cron(Plugin, IndependentPlugin): short_desc = 'Cron job scheduler' plugin_name = "cron" profiles = ('system',) packages = ('cron', 'anacron', 'chronie') files = ('/etc/crontab',) def setup(self): self.add_copy_spec([ "/etc/cron*", "/var/log/cron", "/var/spool/cron" ]) if self.get_option("all_logs"): self.add_copy_spec("/var/log/cron*") self.add_cmd_output("crontab -l -u root", suggest_filename="root_crontab", tags="root_crontab") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/subscription_manager.py0000664000175000017500000001167214660147624021366 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from configparser import NoOptionError, NoSectionError import glob from sos.report.plugins import Plugin, RedHatPlugin class SubscriptionManager(Plugin, RedHatPlugin): short_desc = 'subscription-manager information' plugin_name = 'subscription_manager' profiles = ('system', 'packagemanager', 'sysmgmt') files = ('/etc/rhsm/rhsm.conf',) packages = ('subscription-manager',) def get_proxy_string(self, config): """ return curl options --proxy[-user] per RHSM config """ proxy = "" proxy_hostname = config.get('server', 'proxy_hostname') if proxy_hostname: proxy_scheme = config.get('server', 'proxy_scheme') proxy_port = config.get('server', 'proxy_port') if proxy_port: proxy_port = ":" + proxy_port proxy = f"--proxy {proxy_scheme}://{proxy_hostname}{proxy_port}" proxy_user = config.get('server', 'proxy_user') if proxy and proxy_user: proxy += f" --proxy-user {proxy_user}" proxy_password = config.get('server', 'proxy_password') if proxy_password: proxy += f":{proxy_password}" return proxy def get_server_url(self, config): """ return URL per RHSM config for curl command """ secure = "s" if config.get('server', 'insecure') != '1' else "" port = config.get('server', 'port') # if port is set, prepend it by ':' separating it from hostname if len(port) > 0: port = ":" + port return (f"http{secure}://{config.get('server', 'hostname')}{port}" f"{config.get('server', 'prefix')}") def setup(self): # rhsm config and logs self.add_copy_spec([ "/etc/rhsm/", "/var/lib/rhsm/", "/var/log/rhsm/rhsm.log", "/var/log/rhsm/rhsmcertd.log"]) self.add_cmd_output("subscription-manager identity", tags="subscription_manager_id") self.add_cmd_output("subscription-manager list --consumed", tags="subscription_manager_list_consumed") self.add_cmd_output("subscription-manager list --installed", tags="subscription_manager_installed") self.add_cmd_output([ "subscription-manager list --available", "subscription-manager list --all --available", "subscription-manager release --show", "subscription-manager release --list", "syspurpose show", "subscription-manager syspurpose --show", "subscription-manager status", ], cmd_as_tag=True) self.add_cmd_output("rhsm-debug system --sos --no-archive " "--no-subscriptions --destination " f"{self.get_cmd_output_path()}") certs = glob.glob('/etc/pki/product-default/*.pem') self.add_cmd_output([f"rct cat-cert {cert}" for cert in certs], tags='subscription_manager_installed_product_ids') # try curl to the RHSM server for potential certificate/proxy issue curlcmd = "curl -vv --cacert /etc/rhsm/ca/redhat-uep.pem " \ "https://subscription.rhsm.redhat.com:443/subscription" env = None # for no_proxy try: from rhsm.config import get_config_parser # pylint: disable=C0415 config = get_config_parser() proxy = self.get_proxy_string(config) server_url = self.get_server_url(config) curlcmd = (f"curl -vv {server_url} --cacert " f"{config.get('rhsm', 'repo_ca_cert')} {proxy}") # honour os.environ no_proxy, if set no_proxy = config.get('server', 'no_proxy') if no_proxy: env = {'NO_PROXY': no_proxy} except (ModuleNotFoundError, ImportError, NoOptionError, NoSectionError) as err: self._log_debug(f"Error checking for RHSM cert/proxy issue: {err}") self.add_cmd_output(curlcmd, env=env, timeout=30) def postproc(self): passwdreg = r"(proxy_password(\s)*=(\s)*)(\S+)\n" repl = r"\1********\n" self.do_path_regex_sub("/etc/rhsm/rhsm.conf", passwdreg, repl) # Scrub passwords in repositories # Example of scrubbing: # # password=hackme # To: # password=******** # # Whitespace around '=' is allowed. regexp = r"(password(\s)*=(\s)*)(\S+)\n" repl = r"\1********\n" self.do_path_regex_sub("/var/lib/rhsm/repo_server_val/*", regexp, repl) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/kpatch.py0000664000175000017500000000202614660147624016413 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin class Kpatch(Plugin, RedHatPlugin): short_desc = 'Kpatch information' plugin_name = 'kpatch' packages = ('kpatch',) def setup(self): kpatch_list = self.collect_cmd_output("kpatch list") if not kpatch_list['status'] == 0: return kpatches = kpatch_list['output'].splitlines() for patch in kpatches: if not re.match(r"^kpatch-.*\(.*\)", patch): continue (module, _) = patch.split() self.add_cmd_output("kpatch info " + module) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/btrfs.py0000664000175000017500000000134314660147624016262 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Btrfs(Plugin, IndependentPlugin): short_desc = 'Btrfs filesystem' plugin_name = 'btrfs' profiles = ('storage',) packages = ('btrfs-progs', 'btrfs-tools') def setup(self): self.add_cmd_output([ "btrfs filesystem show", "btrfs version" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/firewalld.py0000664000175000017500000000371214660147624017115 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Jamie Bainbridge # Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class FirewallD(Plugin, RedHatPlugin): short_desc = 'Firewall daemon' plugin_name = 'firewalld' profiles = ('network',) packages = ('firewalld',) def setup(self): self.add_copy_spec("/etc/firewalld/firewalld.conf", tags='firewalld_conf') self.add_copy_spec([ "/etc/firewalld/*.xml", "/etc/firewalld/icmptypes/*.xml", "/etc/firewalld/services/*.xml", "/etc/firewalld/zones/*.xml", "/etc/firewalld/ipsets/*.xml", "/etc/firewalld/policies/*.xml", "/etc/firewalld/helpers/*.xml", "/etc/sysconfig/firewalld", "/var/log/firewalld", ]) # use a 10s timeout to workaround dbus problems in # docker containers. self.add_cmd_output([ "firewall-cmd --direct --get-all-chains", "firewall-cmd --direct --get-all-rules", "firewall-cmd --direct --get-all-passthroughs", "firewall-cmd --get-log-denied", "firewall-cmd --list-all-zones", "firewall-cmd --permanent --list-all-zones", "firewall-cmd --permanent --direct --get-all-chains", "firewall-cmd --permanent --direct --get-all-rules", "firewall-cmd --permanent --direct --get-all-passthroughs", "firewall-cmd --state" ], timeout=10, cmd_as_tag=True) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/storageconsole.py0000664000175000017500000000323714660147624020175 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin class StorageConsole(Plugin, RedHatPlugin, DebianPlugin): short_desc = 'Red Hat Storage Console' plugin_name = 'storageconsole' profiles = ('storage',) packages = ('rhscon-core',) def setup(self): all_logs = self.get_option("all_logs") if not all_logs: self.add_copy_spec([ "/var/log/skyring/skyring.log", "/var/log/skyring/bigfin.log", "/var/log/carbon/console.log", "/var/log/graphite-web/info.log", "/var/log/graphite-web/exception.log", ]) else: self.add_copy_spec([ "/var/log/skyring/", "/var/log/carbon/", "/var/log/graphite-web/" ]) self.add_copy_spec([ "/etc/skyring/", "/etc/carbon/", "/etc/graphite-web/" ]) self.add_cmd_output( "mongo skyring --eval 'db.getCollectionNames()'", suggest_filename="mongo_skyring_collectionnames.txt") self.add_cmd_output( "mongo skyring --eval 'DBQuery.shellBatchSize = 10000;" "db.storage_nodes.find()'", suggest_filename="mongo_skyring_storagenodes.txt") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/containers_common.py0000664000175000017500000000522314660147624020660 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin, PluginOpt class ContainersCommon(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'Common container configs under {/etc,/usr/share}/containers' plugin_name = 'containers_common' profiles = ('container', ) packages = ('containers-common', ) option_list = [ PluginOpt('rootlessusers', default='', val_type=str, desc='colon-delimited list of users to collect for') ] def setup(self): self.add_copy_spec([ '/etc/containers/*', '/usr/share/containers/*', '/etc/subuid', '/etc/subgid', ]) self.add_file_tags({ "/etc/containers/policy.json": "containers_policy" }) users_opt = self.get_option('rootlessusers') users_list = [] if users_opt: users_list = [x for x in users_opt.split(':') if x] user_subcmds = [ 'podman info', 'podman unshare cat /proc/self/uid_map', 'podman unshare cat /proc/self/gid_map', 'podman images', 'podman images --digests', 'podman pod ps', 'podman port --all', 'podman ps', 'podman ps -a', 'podman stats --no-stream --all', 'podman version', 'podman volume ls', 'buildah info', 'buildah unshare cat /proc/self/uid_map', 'buildah unshare cat /proc/self/gid_map', 'buildah containers', 'buildah containers --all', 'buildah images', 'buildah images --all', 'buildah version', ] for user in users_list: # collect user's containers' config expanded_user = os.path.expanduser(f'~{user}') self.add_copy_spec( f'{expanded_user}/.config/containers/') # collect user-status self.add_cmd_output(f'loginctl user-status {user}') # collect the user's related commands self.add_cmd_output([ f'machinectl -q shell {user}@ /usr/bin/{cmd}' for cmd in user_subcmds ], foreground=True) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/greenboot.py0000664000175000017500000000161114660147624017124 0ustar arifarif# Copyright 2023 Red Hat, Inc. Evgeny Slutsky # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Greenboot(Plugin, RedHatPlugin): """The greenboot plugin collects systemd service logs and configuration. """ short_desc = 'Greenboot' plugin_name = 'greenboot' services = (plugin_name, 'greenboot-healthcheck', 'greenboot-task-runner', 'redboot-task-runner',) profiles = ('system',) def setup(self): self.add_copy_spec([ "/etc/greenboot/greenboot.conf", ]) sos-4.8.0/sos/report/plugins/usb.py0000664000175000017500000000133314660147624015732 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Usb(Plugin, IndependentPlugin): short_desc = 'USB devices' plugin_name = "usb" profiles = ('hardware',) def setup(self): self.add_copy_spec("/sys/bus/usb") self.add_cmd_output([ "lsusb", "lsusb -v", "lsusb -t" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ldap.py0000664000175000017500000000576414660147624016075 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Ldap(Plugin): short_desc = 'LDAP configuration' plugin_name = "ldap" profiles = ('identity', 'sysmgmt', 'system') ldap_conf = "/etc/openldap/ldap.conf" def setup(self): super().setup() self.add_copy_spec("/etc/ldap.conf") def postproc(self): self.do_file_sub("/etc/ldap.conf", r"(\s*bindpw\s*)\S+", r"\1******") class RedHatLdap(Ldap, RedHatPlugin): packages = ('openldap', 'nss-pam-ldapd') files = ('/etc/ldap.conf', '/etc/pam_ldap.conf') def setup(self): super().setup() self.add_forbidden_path([ "/etc/openldap/certs/password", "/etc/openldap/certs/pwfile.txt", "/etc/openldap/certs/pin.txt", "/etc/openldap/certs/*passw*", "/etc/openldap/certs/key3.db" ]) self.add_copy_spec([ self.ldap_conf, "/etc/openldap/certs/cert8.db", "/etc/openldap/certs/secmod.db", "/etc/nslcd.conf", "/etc/pam_ldap.conf" ]) self.add_cmd_output("certutil -L -d /etc/openldap") def postproc(self): super().postproc() for file in ["/etc/nslcd.conf", "/etc/pam_ldap.conf"]: self.do_file_sub( file, r"(\s*bindpw\s*)\S+", r"\1********" ) class DebianLdap(Ldap, DebianPlugin, UbuntuPlugin): ldap_conf = "/etc/ldap/ldap.conf" packages = ('slapd', 'ldap-utils') def setup(self): super().setup() ldap_search = "ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// " self.add_copy_spec([ self.ldap_conf, "/etc/slapd.conf", "/etc/ldap/slapd.d", "/etc/nslcd.conf", ]) self.add_cmd_output("ldapsearch -x -b '' -s base 'objectclass=*'") self.add_cmd_output( ldap_search + "-b cn=config '(!(objectClass=olcSchemaConfig))'", suggest_filename="configuration_minus_schemas") self.add_cmd_output( ldap_search + "-b cn=schema,cn=config dn", suggest_filename="loaded_schemas") self.add_cmd_output( ldap_search + "-b cn=config '(olcAccess=*)' olcAccess olcSuffix", suggest_filename="access_control_lists") def postproc(self): super().postproc() self.do_file_sub( "/etc/nslcd.conf", r"(\s*bindpw\s*)\S+", r"\1********" ) self.do_cmd_output_sub( "ldapsearch", r"(olcRootPW\: \s*)\S+", r"\1********" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_sahara.py0000664000175000017500000000525514660147624020456 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc.,Poornima M. Kshirsagar # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackSahara(Plugin): short_desc = 'OpenStack Sahara' plugin_name = 'openstack_sahara' profiles = ('openstack', 'openstack_controller') var_puppet_gen = "/var/lib/config-data/puppet-generated/sahara" def setup(self): self.add_copy_spec([ "/etc/sahara/", self.var_puppet_gen + "/etc/sahara/" ]) self.add_journal(units="openstack-sahara-all") self.add_journal(units="openstack-sahara-api") self.add_journal(units="openstack-sahara-engine") if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/sahara/", ]) else: self.add_copy_spec([ "/var/log/sahara/*.log", ]) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/sahara/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/sahara/*", regexp, subst ) def postproc(self): protect_keys = [ "admin_password", "memcache_secret_key", "password", "qpid_password", "rabbit_password", "ssl_key_password", "xenapi_connection_password", "transport_url" ] connection_keys = ["connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianSahara(OpenStackSahara, DebianPlugin, UbuntuPlugin): short_desc = 'OpenStack Sahara information for Debian based distributions' packages = ( 'sahara-api', 'sahara-common', 'sahara-engine', 'python-sahara', 'python3-sahara', ) class RedHatSahara(OpenStackSahara, RedHatPlugin): short_desc = 'OpenStack Sahara information for Red Hat distributions' packages = ('openstack-selinux',) def setup(self): super().setup() self.add_copy_spec("/etc/sudoers.d/sahara*") # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/gluster.py0000664000175000017500000001263214660147624016632 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob import os import time from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Gluster(Plugin, RedHatPlugin): short_desc = 'GlusterFS storage' plugin_name = 'gluster' profiles = ('storage', 'virt') statedump_dir = '/run/gluster' packages = ("glusterfs", "glusterfs-core") files = ("/etc/glusterd", "/var/lib/glusterd") option_list = [ PluginOpt("dump", default=False, desc="enable glusterdump support") ] def wait_for_statedump(self, name_dir): """ Wait until state dump is done """ statedumps_present = 0 statedump_entries = [ f for f in self.listdir(name_dir) if self.path_isfile(f) ] for statedump_file in statedump_entries: statedumps_present = statedumps_present+1 _spath = self.path_join(name_dir, statedump_file) ret = -1 while ret == -1: with open(_spath, 'r', encoding='UTF-8') as sfile: last_line = sfile.readlines()[-1] ret = last_line.count('DUMP_END_TIME') def postproc(self): if self.get_option("dump"): if not self.path_exists(self.statedump_dir): return try: remove_files = glob.glob(self.statedump_dir + '/*.dump.[0-9]*') remove_files.extend(glob.glob(self.statedump_dir + '/glusterd_state_[0-9]*_[0-9]*')) for name in remove_files: os.remove(name) except OSError as err: self._log_error(f"Could not remove statedump files: {err}") def setup(self): self.add_forbidden_path("/var/lib/glusterd/geo-replication/secret.pem") self.add_forbidden_path( "/var/lib/glusterd/glusterfind/glusterfind_*_secret.pem" ) self.add_cmd_output("gluster peer status", tags="gluster_peer_status") self.add_cmd_output("gluster pool list") self.add_cmd_output("gluster volume status", tags="gluster_v_status") self.add_copy_spec([ "/etc/redhat-storage-release", # collect unified file and object storage configuration "/etc/swift/", # glusterfs-server rpm scripts stash this on migration to 3.3.x "/etc/glusterd.rpmsave", # common to all versions "/etc/glusterfs", "/var/lib/glusterd/", # collect nfs-ganesha related configuration "/run/gluster/shared_storage/nfs-ganesha/", # collect public ssh keys (a_s_c skips implicit hidden files) "/var/lib/glusterd/glusterfind/.keys/", ] + glob.glob('/run/gluster/*tier-dht/*')) if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/glusterfs/*log", "/var/log/glusterfs/*/*log", "/var/log/glusterfs/geo-replication/*/*log" ]) else: self.add_copy_spec("/var/log/glusterfs") if self.get_option("dump"): if self.path_exists(self.statedump_dir): statedump_cmd = "killall -USR1 glusterfs glusterfsd glusterd" if self.exec_cmd(statedump_cmd)['status'] == 0: # let all the processes catch the signal and create # statedump file entries. time.sleep(1) self.wait_for_statedump(self.statedump_dir) self.add_copy_spec(self.statedump_dir) else: self.soslog.info("could not send SIGUSR1 to glusterfs/" "glusterd processes") else: self.soslog.warning("Unable to generate statedumps, no such " "directory: %s", self.statedump_dir) state = self.exec_cmd("gluster get-state") if state['status'] == 0: state_file = state['output'].split()[-1] self.add_copy_spec(state_file) volume_cmd = self.collect_cmd_output("gluster volume info", tags="gluster_v_info") if volume_cmd['status'] == 0: for line in volume_cmd['output'].splitlines(): if not line.startswith("Volume Name:"): continue volname = line[12:] self.add_cmd_output([ f"gluster volume get {volname} all", f"gluster volume geo-replication {volname} status", f"gluster volume heal {volname} info", f"gluster volume heal {volname} info split-brain", f"gluster volume status {volname} clients", f"gluster snapshot list {volname}", f"gluster volume quota {volname} list", f"gluster volume rebalance {volname} status", f"gluster snapshot info {volname}", f"gluster snapshot status {volname}", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dovecot.py0000664000175000017500000000175714660147624016616 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Dovecot(Plugin): short_desc = 'Dovecot IMAP and POP3' plugin_name = "dovecot" profiles = ('mail',) def setup(self): self.add_copy_spec("/etc/dovecot*") self.add_cmd_output("dovecot -n") class RedHatDovecot(Dovecot, RedHatPlugin): """ Parent class Dovecot's setup() will be called """ packages = ('dovecot', ) files = ('/etc/dovecot.conf',) class DebianDovecot(Dovecot, DebianPlugin, UbuntuPlugin): """ Parent class Dovecot's setup() will be called """ files = ('/etc/dovecot/README',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ebpf.py0000664000175000017500000000616114660147624016061 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Ebpf(Plugin, IndependentPlugin): short_desc = 'eBPF tool' plugin_name = 'ebpf' profiles = ('system', 'kernel', 'network') commands = ('bpftool',) option_list = [ PluginOpt("namespaces", default=None, val_type=int, desc="Number of namespaces to collect, 0 for unlimited"), ] def get_bpftool_prog_ids(self, prog_json): """ Collect the list of program IDs """ out = [] try: prog_data = json.loads(prog_json) except Exception as err: # pylint: disable=broad-except self._log_info(f"Couldn't parse bpftool prog list: {err}") return out for _, item in enumerate(prog_data): if "id" in item: out.append(item["id"]) return out def get_bpftool_map_ids(self, map_json): """ Collect the list of mapIDs """ out = [] try: map_data = json.loads(map_json) except Exception as err: # pylint: disable=broad-except self._log_info(f"Could not parse bpftool map list: {err}") return out for _, item in enumerate(map_data): if "id" in item: out.append(item["id"]) return out def setup(self): # collect list of eBPF programs and maps and their dumps progs = self.collect_cmd_output("bpftool -j prog list") for prog_id in self.get_bpftool_prog_ids(progs['output']): for dumpcmd in ["xlated", "jited"]: self.add_cmd_output(f"bpftool prog dump {dumpcmd} id " f"{prog_id}") maps = self.collect_cmd_output("bpftool -j map list") for map_id in self.get_bpftool_map_ids(maps['output']): self.add_cmd_output(f"bpftool map dump id {map_id}") self.add_cmd_output([ # collect list of eBPF programs and maps and their dumps # in human readable form "bpftool prog list", "bpftool map list", # Iterate over all cgroups and list all attached programs "bpftool cgroup tree", # collect list of bpf program attachments in the kernel # networking subsystem "bpftool net list", # collect all struct_ops currently existing in the system "bpftool struct_ops dump" ]) # Capture list of bpf program attachments from namespaces cmd_prefix = "ip netns exec " nsps = self.get_option('namespaces') for namespace in self.get_network_namespaces(ns_max=nsps): ns_cmd_prefix = cmd_prefix + namespace + " " self.add_cmd_output(ns_cmd_prefix + "bpftool net list") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/iscsitarget.py0000664000175000017500000000237314660147624017467 0ustar arifarif# Copyright (C) 2007-2012 Red Hat, Inc., Ben Turner # Copyright (C) 2012 Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class IscsiTarget(Plugin): short_desc = 'iSCSI target' plugin_name = "iscsitarget" profiles = ('storage',) class RedHatIscsiTarget(IscsiTarget, RedHatPlugin): packages = ('scsi-target-utils',) def setup(self): super().setup() self.add_copy_spec("/etc/tgt/targets.conf") self.add_cmd_output("tgtadm --lld iscsi --op show --mode target") class DebianIscsiTarget(IscsiTarget, DebianPlugin, UbuntuPlugin): packages = ('iscsitarget',) def setup(self): super().setup() self.add_copy_spec([ "/etc/iet", "/etc/sysctl.d/30-iscsitarget.conf", "/etc/default/iscsitarget" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sunrpc.py0000664000175000017500000000146214660147624016456 0ustar arifarif# Copyright (C) 2012 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class SunRPC(Plugin, IndependentPlugin): short_desc = 'Sun RPC service' plugin_name = "sunrpc" profiles = ('system', 'storage', 'network', 'nfs') packages = ('rpcbind',) def setup(self): self.add_cmd_output("rpcinfo -p localhost") self.add_copy_spec('/sys/kernel/debug/sunrpc') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_horizon.py0000664000175000017500000000640714660147624020707 0ustar arifarif# Copyright (C) 2009 Red Hat, Inc., Joey Boggs # Copyright (C) 2012 Rackspace US, Inc., # Justin Shepherd # Copyright (C) 2013 Red Hat, Inc., Jeremy Agee # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackHorizon(Plugin): short_desc = 'OpenStack Horizon' plugin_name = "openstack_horizon" profiles = ('openstack', 'openstack_controller') var_puppet_gen = "/var/lib/config-data/puppet-generated" def setup(self): if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/horizon/", ]) else: self.add_copy_spec([ "/var/log/horizon/*.log", ]) self.add_copy_spec([ "/etc/openstack-dashboard/", self.var_puppet_gen + "/horizon/etc/openstack-dashboard/", self.var_puppet_gen + "/horizon/etc/httpd/conf/", self.var_puppet_gen + "/horizon/etc/httpd/conf.d/", self.var_puppet_gen + "/horizon/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "/memcached/etc/sysconfig/memcached" ]) self.add_forbidden_path( "/etc/openstack-dashboard/local_settings.d/*.py[co]" ) def postproc(self): var_puppet_gen = self.var_puppet_gen + "/horizon" protect_keys = [ "SECRET_KEY", "EMAIL_HOST_PASSWORD" ] regexp = fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)" for regpath in [r"/etc/openstack-dashboard/.*\.json", "/etc/openstack-dashboard/local_settings$"]: self.do_path_regex_sub(regpath, regexp, r"\1*********") self.do_path_regex_sub(var_puppet_gen + regpath, regexp, r"\1*********") class DebianHorizon(OpenStackHorizon, DebianPlugin): packages = ( 'python-django-horizon', 'openstack-dashboard', 'openstack-dashboard-apache' ) def setup(self): super().setup() self.add_copy_spec("/etc/apache2/sites-available/") class UbuntuHorizon(OpenStackHorizon, UbuntuPlugin): packages = ( 'python-django-horizon', 'python3-django-horizon', 'openstack-dashboard', 'openstack-dashboard-ubuntu-theme', ) def setup(self): super().setup() self.add_copy_spec("/etc/apache2/conf.d/openstack-dashboard.conf") class RedHatHorizon(OpenStackHorizon, RedHatPlugin): packages = ('openstack-selinux',) def setup(self): super().setup() self.add_copy_spec("/etc/httpd/conf.d/openstack-dashboard.conf") if self.get_option("all_logs"): self.add_copy_spec("/var/log/httpd/horizon*") else: self.add_copy_spec([ "/var/log/httpd/horizon*.log" "/var/log/httpd/" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/hts.py0000664000175000017500000000127414660147624015743 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class HardwareTestSuite(Plugin, RedHatPlugin): short_desc = 'Red Hat Hardware Test Suite' plugin_name = 'hts' profiles = ('debug',) def setup(self): self.add_copy_spec([ "/etc/httpd/conf.d/hts.conf", "/var/hts" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/omnipath_manager.py0000664000175000017500000000471314660147624020457 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Pavel Moravec # 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. # 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 sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class OmnipathManager(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'OmniPath Fabric Manager' plugin_name = 'omnipath_manager' profiles = ('hardware',) packages = ('opa-fm',) services = ('opa-fm',) def setup(self): # Use absolute paths for the opa-fm binaries since they are installed # in a non-standard location (sos policies do not evaluate drop-in # files from /etc/profile.d). self.add_cmd_output([ "/usr/lib/opa-fm/bin/config_check -v -d -s", "/usr/lib/opa-fm/bin/fm_cmdall smAdaptiveRouting", "/usr/lib/opa-fm/bin/fm_cmdall smLooptestShowConfig", "/usr/lib/opa-fm/bin/fm_cmdall smLooptestShowTopology", "/usr/lib/opa-fm/bin/fm_cmdall smLooptestShowSwitchLft", "/usr/lib/opa-fm/bin/fm_cmdall smLooptestShowLoopPaths", "/usr/lib/opa-fm/bin/fm_cmdall pmShowCounters", "/usr/lib/opa-fm/bin/fm_cmdall smShowCounters", ]) # fm_capture generates a dated tgz file in the current directory only # so change dir to sos_commands/, collect the tarball directly # there now, and change dir back. This is unfortunate but is the only # way to collect this since fm_capture has no option to set the output # path or file name. # # This may also need to be amended for other distributions if these # binaries are placed in an alternative location (e.g. /usr/libexec). self.add_cmd_output("/usr/lib/opa-fm/bin/fm_capture", runat=self.get_cmd_output_path()) self.add_copy_spec("/etc/opa-fm/opafm.xml") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_octavia.py0000664000175000017500000001224414660147624020641 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackOctavia(Plugin): short_desc = 'Openstack Octavia' plugin_name = "openstack_octavia" profiles = ('openstack', 'openstack_controller') var_config_data = "/var/lib/config-data" var_puppet_gen = var_config_data + "/puppet-generated/octavia" resources = [ 'amphora', 'availabilityzone', 'availabilityzoneprofile', 'flavor', 'flavorprofile', 'healthmonitor', 'l7policy', 'listener', 'pool', 'provider', 'quota' ] def setup(self): # configs self.add_copy_spec([ "/etc/sysconfig/network-scripts/ifcfg-o-hm0", "/etc/logrotate.d/openstack-octavia", "/etc/octavia/*", "/var/lib/octavia", self.var_config_data + "/octavia/etc/octavia", self.var_puppet_gen + "/etc/octavia", self.var_puppet_gen + "/etc/rsyslog.d", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf", ]) self.add_file_tags({ ".*/etc/octavia/octavia.conf": "octavia_conf" }) # don't collect certificates self.add_forbidden_path("/etc/octavia/certs") self.add_forbidden_path(self.var_config_data + "/etc/octavia/certs") self.add_forbidden_path(self.var_puppet_gen + "/etc/octavia/certs") # logs if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/octavia/*", ]) else: self.add_copy_spec([ "/var/log/octavia/*.log", ]) # commands vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)) and not \ (self.is_installed("python2-octaviaclient") or self.is_installed("python3-octaviaclient")): self.soslog.warning("Not all environment variables set or " "octavia client package not installed." "Source the environment file for the " "user intended to connect to the " "OpenStack environment and install " "octavia client package.") else: self.add_cmd_output('openstack loadbalancer list', subdir='loadbalancer') for res in self.resources: # get a list for each resource type self.add_cmd_output(f'openstack loadbalancer {res} list', subdir=res) # get details from each resource cmd = f"openstack loadbalancer {res} list -f value -c id" ret = self.exec_cmd(cmd) if ret['status'] == 0: for ent in ret['output'].splitlines(): ent = ent.split()[0] self.add_cmd_output( f"openstack loadbalancer {res} show {ent}", subdir=res) # get capability details from each provider cmd = "openstack loadbalancer provider list -f value -c name" ret = self.exec_cmd(cmd) if ret['status'] == 0: for provider in ret['output'].splitlines(): provider = provider.split()[0] self.add_cmd_output( "openstack loadbalancer provider capability list" f" {provider}", subdir='provider_capability') def postproc(self): protect_keys = [ "ca_private_key_passphrase", "heartbeat_key", "password", "connection", "transport_url", "server_certs_key_passphrase", "memcache_secret_key" ] regexp = fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)" self.do_path_regex_sub("/etc/octavia/*", regexp, r"\1*********") self.do_path_regex_sub( self.var_puppet_gen + "/etc/octavia/*", regexp, r"\1*********" ) class DebianOctavia(OpenStackOctavia, DebianPlugin, UbuntuPlugin): packages = ( 'octavia-common', 'octavia-api', 'python3-octavia', ) def setup(self): super().setup() if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/apache2/octavia*", ]) else: self.add_copy_spec([ "/var/log/apache2/octavia*.log", ]) class RedHatOctavia(OpenStackOctavia, RedHatPlugin): packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/megacli.py0000664000175000017500000000201714660147624016542 0ustar arifarif# megacli.py # Copyright (C) 2007-2014 Red Hat, Inc., Jon Magrini # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class MegaCLI(Plugin, RedHatPlugin): short_desc = 'LSI MegaRAID devices' plugin_name = 'megacli' profiles = ('system', 'storage', 'hardware') files = ('/opt/MegaRAID/MegaCli/MegaCli64',) def setup(self): cmd = '/opt/MegaRAID/MegaCli/MegaCli64' subcmds = [ 'LDPDInfo', '-AdpAllInfo', '-AdpBbuCmd -GetBbuStatus', '-ShowSummary' ] self.add_cmd_output([ f"{cmd} {subcmd} -aALL" for subcmd in subcmds ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sunbeam_hypervisor.py0000664000175000017500000000571214660147624021072 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class SunbeamHypervisor(Plugin, UbuntuPlugin): short_desc = "Sunbeam Hypervisor" plugin_name = "sunbeam_hypervisor" profiles = ('cloud',) packages = ('openstack-hypervisor',) common_dir = '/var/snap/openstack-hypervisor/common' def setup(self): self.add_service_status('snap.openstack-hypervisor.*') self.add_journal('nova-compute') self.add_copy_spec([ f'{self.common_dir}/*.log', f'{self.common_dir}/log/**/*.log', f'{self.common_dir}/etc', f'{self.common_dir}/lib/nova/instances/*/console.log', f'{self.common_dir}/cache/libvirt/qemu/capabilities/*.xml', ]) self.add_forbidden_path([ f'{self.common_dir}/etc/ssl/', f'{self.common_dir}/etc/libvirt/secrets', f'{self.common_dir}/etc/libvirt/passwd.db', f'{self.common_dir}/etc/libvirt/krb5.tab', f'{self.common_dir}/var/log/ovn/', ]) def postproc(self): # libvirt confs match_exp = r"(\s*passwd=\s*')([^']*)('.*)" libvirt_path_exps = [ fr"{self.common_dir}/etc/libvirt/qemu/.*\.xml", fr"{self.common_dir}/etc/libvirt/.*\.conf" ] for path_exp in libvirt_path_exps: self.do_path_regex_sub(path_exp, match_exp, r"\1******\3") # nova/neutron bits protect_keys = [ ".*_key", ".*_pass(wd|word)?", "metadata_proxy_shared_secret", "password", "rbd_secret_uuid", "server_auth", "serverauth", "transport_url", ] connection_keys = ["connection", "sql_connection"] self.do_path_regex_sub( fr"{self.common_dir}/etc/(nova|neutron|ceilometer)/*", fr'(^\s*({"|".join(protect_keys)})\s*=\s*)(.*)', r"\1*********" ) self.do_path_regex_sub( fr"{self.common_dir}/etc/(nova|neutron|ceilometer)/*", fr'(^\s*({"|".join(connection_keys)})\s*=\s*(.*)' r'://(\w*):)(.*)(@(.*))', r"\1*********\6" ) # hooks.log protect_hook_keys = [ "password", "ovn_metadata_proxy_shared_secret", "cacert", "cert", "key", "ovn_cacert", "ovn_cert", "ovn_key", ] self.do_file_sub( f'{self.common_dir}/hooks.log', fr'(\'({"|".join(protect_hook_keys)})\'):\s?\'(.+?)\'', r"\1: **********" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/podman.py0000664000175000017500000001266514660147624016431 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. Daniel Walsh # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin, PluginOpt class Podman(Plugin, RedHatPlugin, UbuntuPlugin): """Podman is a daemonless container management engine, and this plugin is meant to provide diagnostic information for both the engine and the containers that podman is managing. General status information will be collected from podman commands, while detailed inspections of certain components will provide more insight into specific container problems. This detailed inspection is provided for containers, images, networks, and volumes. Per-entity inspections will be recorded in subdirs within sos_commands/podman/ for each of those types. """ short_desc = 'Podman containers' plugin_name = 'podman' profiles = ('container',) packages = ('podman',) option_list = [ PluginOpt('all', default=False, desc='collect for all containers, even terminated ones', long_desc=( 'Enable collection for all containers that exist on the ' 'system regardless of their running state. This may cause ' 'a significant increase in sos archive size, especially ' 'when combined with the \'logs\' option.')), PluginOpt('logs', default=False, desc='collect stdout/stderr logs for containers', long_desc=( 'Capture \'podman logs\' output for discovered containers.' ' This may be useful or not depending on how/if the ' 'container produces stdout/stderr output. Use cautiously ' 'when also using the \'all\' option.')), PluginOpt('size', default=False, desc='collect image sizes for podman ps') ] def setup(self): self.add_env_var([ 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'ALL_PROXY' ]) self.add_cmd_tags({ 'podman images': 'podman_list_images', 'podman ps': 'podman_list_containers' }) subcmds = [ 'info', 'images', 'images --digests', 'pod ps', 'port --all', 'ps', 'ps -a', 'stats --no-stream --all', 'version', 'volume ls' ] self.add_cmd_output([f"podman {s}" for s in subcmds]) # separately grab ps -s as this can take a *very* long time if self.get_option('size'): self.add_cmd_output('podman ps -as', priority=100) self.add_dir_listing([ '/etc/cni', '/etc/containers' ], recursive=True) pnets = self.collect_cmd_output('podman network ls', tags='podman_list_networks') if pnets['status'] == 0: nets = [pn.split()[0] for pn in pnets['output'].splitlines()[1:]] self.add_cmd_output([ f"podman network inspect {net}" for net in nets ], subdir='networks', tags='podman_network_inspect') containers = [ c[0] for c in self.get_containers(runtime='podman', get_all=self.get_option('all')) ] images = self.get_container_images(runtime='podman') volumes = self.get_container_volumes(runtime='podman') for container in containers: self.add_cmd_output(f"podman inspect {container}", subdir='containers', tags='podman_container_inspect') for img in images: name, img_id = img insp = name if 'none' not in name else img_id self.add_cmd_output(f"podman inspect {insp}", subdir='images', tags='podman_image_inspect') for vol in volumes: self.add_cmd_output(f"podman volume inspect {vol}", subdir='volumes', tags='podman_volume_inspect') if self.get_option('logs'): for con in containers: self.add_cmd_output(f"podman logs -t {con}", subdir='containers', priority=50) def postproc(self): # Attempts to match key=value pairs inside container inspect output # for potentially sensitive items like env vars that contain passwords. # Typically, these will be seen in env elements or similar, and look # like this: # "Env": [ # "mypassword=supersecret", # "container=oci" # ], # This will mask values when the variable name looks like it may be # something worth obfuscating. env_regexp = r'(?P(pass|key|secret|PASS|KEY|SECRET).*?)=' \ '(?P.*?)"' self.do_cmd_output_sub('*inspect*', env_regexp, r'\g=********"') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_osd.py0000664000175000017500000001234514660147624016732 0ustar arifarif# Copyright (C) 2023 Canonical Ltd., Nikhil Kshirsagar # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephOSD(Plugin, RedHatPlugin, UbuntuPlugin): """ This plugin is for capturing information from Ceph OSD nodes. While the majority of this plugin should be version agnostic, several collections are dependent upon the version of Ceph installed. Versions that correlate to RHCS 4 or RHCS 5 are explicitly handled for differences such as those pertaining to log locations on the host filesystem. Note that while this plugin will activate based on the presence of Ceph containers, commands are run directly on the host as those containers are often not configured to successfully run the `ceph` commands collected by this plugin. These commands are majorly `ceph daemon` commands that will reference discovered admin sockets under /var/run/ceph. """ short_desc = 'CEPH osd' plugin_name = 'ceph_osd' profiles = ('storage', 'virt', 'container', 'ceph') containers = ('ceph-(.*-)?osd.*',) files = ('/var/lib/ceph/osd/*', '/var/lib/ceph/*/osd*', '/var/snap/microceph/common/data/osd/*') def setup(self): all_logs = self.get_option("all_logs") directory = '' microceph_pkg = self.policy.package_manager.pkg_by_name('microceph') cmds = [ # will work pre quincy "bluestore bluefs available", "dump_reservations", # will work quincy onward "bluefs stats", "bluestore bluefs device info", "config diff", "config show", "counter dump", "counter schema", "dump_blocked_ops", "dump_blocklist", "dump_historic_ops_by_duration", "dump_historic_slow_ops", "dump_mempools", "dump_op_pq_state", "dump_ops_in_flight", "dump_osd_network", "dump_pgstate_history", "dump_recovery_reservations", "dump_scrubs", "dump_watchers", "get_mapped_pools", "list_devices", "list_unfound", "log dump", "objecter_requests", "ops", "perf dump", "perf histogram dump", "perf schema", "status", "version", ] if not microceph_pkg: directory = '/var/run/ceph' self.add_file_tags({ "/var/log/ceph/(.*/)?ceph-(.*-)?osd.*.log": 'ceph_osd_log', }) self.add_forbidden_path([ "/etc/ceph/*keyring*", "/var/lib/ceph/**/*keyring*", # Excludes temporary ceph-osd mount location like # /var/lib/ceph/tmp/mnt.XXXX from sos collection. "/var/lib/ceph/**/tmp/*mnt*", "/etc/ceph/*bindpass*" ]) # Only collect OSD specific files self.add_copy_spec([ "/run/ceph/**/ceph-osd*", "/var/lib/ceph/**/kv_backend", "/var/log/ceph/**/ceph-osd*.log", "/var/log/ceph/**/ceph-volume*.log", ]) self.add_cmd_output([ "ceph-disk list", "ceph-volume lvm list" ]) if all_logs: self.add_copy_spec([ "/var/log/ceph/**/ceph-osd*.log*", "/var/log/ceph/**/ceph-volume*.log*", ]) else: directory = '/var/snap/microceph/current/run' # Only collect microceph files, don't run any commands self.add_forbidden_path([ "/var/snap/microceph/common/**/*keyring*", "/var/snap/microceph/current/**/*keyring*", "/var/snap/microceph/common/state/*", ]) self.add_copy_spec([ "/var/snap/microceph/common/data/osd/*", "/var/snap/microceph/common/logs/*ceph-osd*.log", ]) if all_logs: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-osd*.log*", ]) # common add_cmd_output for ceph and microceph self.add_cmd_output([ f"ceph daemon {i} {c}" for i in self.get_socks(directory) for c in cmds] ) def get_socks(self, directory): """ Find any available admin sockets under /var/run/ceph (or subdirs for later versions of Ceph) which can be used for ceph daemon commands """ ceph_sockets = [] for rdir, _, files in os.walk(directory): for file in files: if file.endswith('.asok') and 'osd' in file: ceph_sockets.append(self.path_join(rdir, file)) return ceph_sockets # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kdump.py0000664000175000017500000001306314660147624016264 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import platform from sos.report.plugins import Plugin, PluginOpt, RedHatPlugin, DebianPlugin, \ UbuntuPlugin, CosPlugin, AzurePlugin class KDump(Plugin): short_desc = 'Kdump crash dumps' plugin_name = "kdump" profiles = ('system', 'debug') def setup(self): self.add_copy_spec([ "/proc/cmdline", "/etc/sysconfig/kdump", "/proc/sys/kernel/panic", "/proc/sys/kernel/panic_on_oops", "/sys/kernel/kexec_loaded", "/sys/kernel/fadump_enabled", "/sys/kernel/fadump/enabled", "/sys/kernel/fadump_registered", "/sys/kernel/fadump/registered", "/sys/kernel/fadump/mem_reserved", "/sys/kernel/kexec_crash_size" ]) self.add_copy_spec("/sys/kernel/kexec_crash_loaded", tags="kexec_crash_loaded") class RedHatKDump(KDump, RedHatPlugin): files = ('/etc/kdump.conf',) packages = ('kexec-tools',) option_list = [ PluginOpt("get-vm-core", default=False, val_type=bool, desc="collect vm core") ] def fstab_parse_fs(self, device): """ Parse /etc/fstab file """ fstab = self.path_join('/etc/fstab') with open(fstab, 'r', encoding='UTF-8') as file: for line in file: if line.startswith((device)): return line.split()[1].rstrip('/') return "" def read_kdump_conffile(self): """ Parse /etc/kdump file """ fsys = "" path = "/var/crash" kdump = '/etc/kdump.conf' with open(kdump, 'r', encoding='UTF-8') as file: for line in file: if line.startswith("path"): path = line.split()[1] elif line.startswith(("ext2", "ext3", "ext4", "xfs")): device = line.split()[1] fsys = self.fstab_parse_fs(device) return fsys + path def setup(self): super().setup() initramfs_img = "/boot/initramfs-" + platform.release() \ + "kdump.img" if self.path_exists(initramfs_img): self.add_cmd_output(f"lsinitrd {initramfs_img}") self.add_copy_spec([ "/etc/kdump.conf", "/etc/udev/rules.d/*kexec.rules", "/usr/lib/udev/rules.d/*kexec.rules", "/var/crash/*/kexec-dmesg.log", "/var/log/kdump.log" ]) self.add_copy_spec("/var/crash/*/vmcore-dmesg.txt", tags="vmcore_dmesg") try: path = self.read_kdump_conffile() except Exception: # pylint: disable=broad-except # set no filesystem and default path path = "/var/crash" self.add_dir_listing(path, recursive=True) self.add_copy_spec(f"{path}/*/vmcore-dmesg.txt") self.add_copy_spec(f"{path}/*/kexec-dmesg.log") # collect the latest vmcore created in the last 24hrs <= 2GB if self.get_option("get-vm-core"): self.add_copy_spec(f"{path}/*/vmcore", sizelimit=2048, maxage=24) class DebianKDump(KDump, DebianPlugin, UbuntuPlugin): files = ('/etc/default/kdump-tools',) packages = ('kdump-tools',) def setup(self): super().setup() initramfs_img = "/var/lib/kdump/initrd.img-" + platform.release() if self.path_exists(initramfs_img): self.add_cmd_output(f"lsinitramfs -l {initramfs_img}") self.add_cmd_output("kdump-config show") self.add_copy_spec([ "/etc/default/kdump-tools" ]) class CosKDump(KDump, CosPlugin): option_list = [ PluginOpt(name="collect-kdumps", default=False, desc="Collect existing kdump files"), ] def setup(self): super().setup() self.add_dir_listing('/var/kdump*', recursive=True) if self.get_option("collect-kdumps"): self.add_copy_spec(["/var/kdump-*"]) class AzureKDump(KDump, AzurePlugin): files = ('/etc/kdump.conf',) packages = ('kexec-tools',) option_list = [ PluginOpt("get-vm-core", default=False, val_type=bool, desc="collect vm core") ] def read_kdump_conffile(self): """ Parse /etc/kdump file """ path = "/var/crash" kdump = '/etc/kdump.conf' with open(kdump, 'r', encoding='UTF-8') as file: for line in file: if line.startswith("path"): path = line.split()[1] return path def setup(self): super().setup() self.add_copy_spec([ "/etc/kdump.conf", "/usr/lib/udev/rules.d/*kexec.rules" ]) try: path = self.read_kdump_conffile() except Exception: # pylint: disable=broad-except # set no filesystem and default path path = "/var/crash" self.add_dir_listing(path, recursive=True) self.add_copy_spec(f"{path}/*/vmcore-dmesg.txt") self.add_copy_spec(f"{path}/*/kexec-dmesg.log") # collect the latest vmcore created in the last 24hrs <= 2GB if self.get_option("get-vm-core"): self.add_copy_spec(f"{path}/*/vmcore", sizelimit=2048, maxage=24) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/unpackaged.py0000664000175000017500000000675314660147624017256 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from pathlib import Path import os import stat from sos.report.plugins import Plugin, RedHatPlugin class Unpackaged(Plugin, RedHatPlugin): short_desc = ('Collects a list of files that are not handled by the ' 'package manager') plugin_name = 'unpackaged' def collect(self): def get_env_path_list(): """Return a list of directories in $PATH. """ return os.environ['PATH'].split(':') def all_files_system(path, exclude=None): """Return a list of all files present on the system, excluding any directories listed in `exclude`. :param path: the starting path :param exclude: list of paths to exclude """ file_list = [] for root, dirs, files in os.walk(path, topdown=True): if exclude: for exc in exclude: dirs[:] = [d for d in dirs if d not in exc] for name in files: path = self.path_join(root, name) try: if stat.S_ISLNK(os.lstat(path).st_mode): path = Path(path).resolve() except Exception: # pylint: disable=broad-except continue file_list.append( [self.path_join(root, name), os.path.realpath(path)] ) for name in dirs: name = self.path_join(root, name) file_list.append([name, os.path.realpath(name)]) return file_list def format_output(files): """Format the unpackaged list as a string. """ expanded = [] for file in files: file = self.path_join(file) out = f"{file}" links = 0 # expand links like # /usr/bin/jfr -> /etc/alternatives/jfr -> # /usr/lib/jvm/java-11-openjdk-11.0.17.0.8-2.el9.x86_64/bin/jfr # but stop at level 10 to prevent potential recursive links while self.path_islink(file) and links < 10: file = os.readlink(file) out += f" -> {file}" links += 1 expanded.append(out + '\n') return expanded # Check command predicate to avoid costly processing if not self.test_predicate(cmd=True): return with self.collection_file('unpackaged') as ufile: paths = get_env_path_list() all_fsystem = [] all_frpm = set( os.path.realpath(x) for x in self.policy.mangle_package_path( self.policy.package_manager.all_files() ) if any(x.startswith(p) for p in paths) ) for path in paths: all_fsystem += all_files_system(path) not_packaged = [x for [x, rp] in all_fsystem if rp not in all_frpm] not_packaged_expanded = format_output(not_packaged) ufile.write(''.join(not_packaged_expanded)) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/usbguard.py0000664000175000017500000000145514660147624016762 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class UsbGuard(Plugin, IndependentPlugin): short_desc = 'USB device usage policy' plugin_name = "usbguard" profiles = ('system',) packages = ('usbguard',) commands = ('usbguard',) def setup(self): self.add_copy_spec("/etc/usbguard") self.add_cmd_output([ "usbguard list-devices", "usbguard list-rules" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_barbican.py0000664000175000017500000000341714660147624020756 0ustar arifarif# Copyright (C) 2019 Mirantis, Inc., Denis Egorenko # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, DebianPlugin, UbuntuPlugin class OpenStackBarbican(Plugin, DebianPlugin, UbuntuPlugin): short_desc = "OpenStack Barbican Secure storage service" plugin_name = "openstack_barbican" profiles = ('openstack', 'openstack_controller') packages = ( 'barbican-common', 'barbican-keystone-listener', 'barbican-worker' ) requires_root = False def setup(self): self.add_copy_spec("/etc/barbican/") if self.get_option("all_logs"): self.add_copy_spec("/var/log/barbican/*") else: self.add_copy_spec("/var/log/barbican/*.log") self.add_forbidden_path("/etc/barbican/*.pem") self.add_forbidden_path("/etc/barbican/alias/*") def postproc(self): protect_keys = [ "password", "rabbit_password", "memcache_secret_key" ] self.do_file_sub( "/etc/barbican/barbican.conf", fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1********" ) connection_keys = ["transport_url", "sql_connection"] join_con_keys = "|".join(connection_keys) self.do_path_regex_sub( "/etc/barbican/barbican.conf", fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/foreman.py0000664000175000017500000003516514660147624016602 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # Copyright (C) 2013 Red Hat, Inc., Lukas Zapletal # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from re import match from shlex import quote from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Foreman(Plugin): short_desc = 'Foreman/Satellite systems management' plugin_name = 'foreman' plugin_timeout = 1800 profiles = ('sysmgmt',) packages = ('foreman',) apachepkg = None dbhost = "localhost" dbpasswd = "" env = {"PGPASSWORD": ""} option_list = [ PluginOpt('days', default=14, desc='number of days for dynflow output'), PluginOpt('proxyfeatures', default=False, desc='collect features of smart proxies'), PluginOpt('puma-gc', default=False, desc='collect Puma GC stats') ] pumactl = 'pumactl %s -S /usr/share/foreman/tmp/puma.state' def setup(self): # for external DB, search in /etc/foreman/database.yml for: # production: # .. # host: some.hostname production_scope = False try: foreman_db = '/etc/foreman/database.yml' with open(foreman_db, 'r', encoding='UTF-8') as dfile: foreman_lines = dfile.read().splitlines() for line in foreman_lines: # skip empty lines and lines with comments if not line or line[0] == '#': continue if line.startswith("production:"): production_scope = True continue if production_scope and match(r"\s+host:\s+\S+", line): self.dbhost = line.split()[1] if production_scope and match(r"\s+password:\s+\S+", line): self.dbpasswd = line.split()[1] # if line starts with a text, it is a different scope if not line.startswith(" "): production_scope = False except IOError: # fallback when the cfg file is not accessible pass # strip wrapping ".." or '..' around password if (self.dbpasswd.startswith('"') and self.dbpasswd.endswith('"')) or \ (self.dbpasswd.startswith('\'') and self.dbpasswd.endswith('\'')): self.dbpasswd = self.dbpasswd[1:-1] # set the password to os.environ when calling psql commands to prevent # printing it in sos logs # we can't set os.environ directly now: other plugins can overwrite it self.env = {"PGPASSWORD": self.dbpasswd} self.add_file_tags({ '/var/log/foreman/production.log.*': 'foreman_production_log', '/etc/sysconfig/foreman-tasks': 'foreman_tasks_config', '/etc/sysconfig/dynflowd': 'foreman_tasks_config', '/var/log/httpd/foreman-ssl_access_ssl.log': 'foreman_ssl_access_ssl_log' }) self.add_forbidden_path([ "/etc/foreman/*key.pem", "/etc/foreman/encryption_key.rb" ]) _hostname = self.exec_cmd('hostname')['output'] _hostname = _hostname.strip() _host_f = self.exec_cmd('hostname -f')['output'] _host_f = _host_f.strip() # Collect these completely everytime self.add_copy_spec([ "/var/log/foreman/production.log", f"/var/log/{self.apachepkg}*/foreman-ssl_*_ssl.log" ], sizelimit=0) # Allow limiting these self.add_copy_spec([ "/etc/foreman/", "/etc/sysconfig/foreman", "/etc/sysconfig/dynflowd", "/etc/default/foreman", "/var/log/foreman/dynflow_executor*log*", "/var/log/foreman/dynflow_executor*.output*", "/var/log/foreman/apipie_cache*.log*", "/var/log/foreman/cron*.log*", "/var/log/foreman/db_migrate*log*", "/var/log/foreman/db_seed*log*", "/var/log/foreman/production.log[.-]*", "/var/log/foreman-selinux-install.log", "/var/log/foreman-proxy-certs-generate*", "/usr/share/foreman/Gemfile*", f"/var/log/{self.apachepkg}*/foreman*", f"/var/log/{self.apachepkg}*/katello-reverse-proxy_error_ssl.log*", f"/var/log/{self.apachepkg}*/error_log*", f"/etc/{self.apachepkg}*/conf/", f"/etc/{self.apachepkg}*/conf.d/", f"/var/log/{self.apachepkg}*/katello-reverse-proxy_access_ssl.log*" ]) self.add_cmd_output([ 'foreman-selinux-relabel -nv', 'passenger-status --show pool', 'passenger-status --show requests', 'passenger-status --show backtraces', 'passenger-memory-stats', f'ping -c1 -W1 {_hostname}', f'ping -c1 -W1 {_host_f}', 'ping -c1 -W1 localhost' ]) self.add_dir_listing([ '/root/ssl-build', '/usr/share/foreman/config/hooks' ], recursive=True) self.add_cmd_output( 'qpid-stat -b amqps://localhost:5671 -q \ --ssl-certificate=/etc/pki/katello/qpid_router_client.crt \ --ssl-key=/etc/pki/katello/qpid_router_client.key \ --sasl-mechanism=ANONYMOUS', suggest_filename='qpid-stat_-q' ) self.add_cmd_output("hammer ping", tags="hammer_ping", timeout=120) # Dynflow Sidekiq self.add_cmd_output('systemctl list-units dynflow*', suggest_filename='dynflow_units') self.add_service_status('"system-dynflow\\x2dsidekiq.slice"', suggest_filename='dynflow_sidekiq_status') self.add_journal(units="dynflow-sidekiq@*") # Puma stats & status, i.e. foreman-puma-stats, then # pumactl stats -S /usr/share/foreman/tmp/puma.state # and optionally also gc-stats # if on RHEL with Software Collections, wrap the commands accordingly if self.get_option('puma-gc'): self.add_cmd_output(self.pumactl % 'gc-stats', suggest_filename='pumactl_gc-stats') self.add_cmd_output(self.pumactl % 'stats', suggest_filename='pumactl_stats') self.add_cmd_output('/usr/sbin/foreman-puma-status') # collect tables sizes, ordered _cmd = self.build_query_cmd( "SELECT table_name, pg_size_pretty(total_bytes) AS total, " "pg_size_pretty(index_bytes) AS INDEX , " "pg_size_pretty(toast_bytes) AS toast, pg_size_pretty(table_bytes)" " AS TABLE FROM ( SELECT *, " "total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes " "FROM (SELECT c.oid,nspname AS table_schema, relname AS " "TABLE_NAME, c.reltuples AS row_estimate, " "pg_total_relation_size(c.oid) AS total_bytes, " "pg_indexes_size(c.oid) AS index_bytes, " "pg_total_relation_size(reltoastrelid) AS toast_bytes " "FROM pg_class c LEFT JOIN pg_namespace n ON " "n.oid = c.relnamespace WHERE relkind = 'r') a) a order by " "total_bytes DESC" ) self.add_cmd_output(_cmd, suggest_filename='foreman_db_tables_sizes', env=self.env) self.collect_foreman_db() self.collect_proxies() def collect_foreman_db(self): # pylint: disable=too-many-locals """ Collect foreman db and dynflow data """ days = f'{self.get_option("days")} days' interval = quote(days) # Construct the DB queries, using the days option to limit the range # of entries returned scmd = ( "select id,name,value from settings where name not similar to " "'%(pass|key|secret)%'" ) dtaskcmd = ('select * from foreman_tasks_tasks where started_at > ' f'NOW() - interval {interval} order by started_at asc') dyncmd = ( 'select dynflow_execution_plans.* from foreman_tasks_tasks join ' 'dynflow_execution_plans on (foreman_tasks_tasks.external_id = ' 'dynflow_execution_plans.uuid::varchar) where foreman_tasks_tasks.' f'started_at > NOW() - interval {interval} order by ' 'foreman_tasks_tasks.started_at asc') dactioncmd = ( 'select dynflow_actions.* from foreman_tasks_tasks join ' 'dynflow_actions on (foreman_tasks_tasks.external_id = ' 'dynflow_actions.execution_plan_uuid::varchar) where ' f'foreman_tasks_tasks.started_at > NOW() - interval {interval} ' 'order by foreman_tasks_tasks.started_at asc') dstepscmd = ( 'select dynflow_steps.* from foreman_tasks_tasks join ' 'dynflow_steps on (foreman_tasks_tasks.external_id = ' 'dynflow_steps.execution_plan_uuid::varchar) where ' f'foreman_tasks_tasks.started_at > NOW() - interval {interval} ' 'order by foreman_tasks_tasks.started_at asc') # counts of fact_names prefixes/types: much of one type suggests # performance issues factnamescmd = ( 'WITH prefix_counts AS (SELECT split_part(name,\'::\',1) FROM ' 'fact_names) SELECT COUNT(*), split_part AS "fact_name_prefix" ' 'FROM prefix_counts GROUP BY split_part ORDER BY count DESC ' 'LIMIT 100' ) # Populate this dict with DB queries that should be saved directly as # postgres formats them. The key will be the filename in the foreman # plugin directory, with the value being the DB query to run foremandb = { 'foreman_settings_table': scmd, 'foreman_schema_migrations': 'select * from schema_migrations', 'foreman_auth_table': 'select id,type,name,host,port,account,' 'base_dn,attr_login,onthefly_register,tls ' 'from auth_sources', 'dynflow_schema_info': 'select * from dynflow_schema_info', 'audits_table_count': 'select count(*) from audits', 'logs_table_count': 'select count(*) from logs', 'fact_names_prefixes': factnamescmd, 'smart_proxies': 'select name,url,download_policy ' + 'from smart_proxies' } # Same as above, but tasks should be in CSV output foremancsv = { 'foreman_tasks_tasks': dtaskcmd, 'dynflow_execution_plans': dyncmd, 'dynflow_actions': dactioncmd, 'dynflow_steps': dstepscmd, } for table, val in foremandb.items(): _cmd = self.build_query_cmd(val) self.add_cmd_output(_cmd, suggest_filename=table, timeout=600, sizelimit=100, env=self.env) # dynflow* tables on dynflow >=1.6.3 are encoded and hence in that # case, psql-msgpack-decode wrapper tool from dynflow-utils (any # version) must be used instead of plain psql command dynutils = self.is_installed('dynflow-utils') for dyn, val in foremancsv.items(): binary = "psql" if dyn != 'foreman_tasks_tasks' and dynutils: binary = "/usr/libexec/psql-msgpack-decode" _cmd = self.build_query_cmd(val, csv=True, binary=binary) self.add_cmd_output(_cmd, suggest_filename=dyn, timeout=600, sizelimit=100, env=self.env) def collect_proxies(self): """ Collect foreman proxies """ if self.get_option('proxyfeatures'): # get a list of proxy names and URLs, and query for their features # store results in smart_proxies_features subdirectory _cmd = self.build_query_cmd('select name,url from smart_proxies', csv=True) proxies = self.exec_cmd(_cmd, env=self.env) if proxies['status'] == 0: # output contains header as the first line, skip it for proxy in proxies['output'].splitlines()[1:]: proxy = proxy.split(',') # proxy is now tuple [name, url] _cmd = 'curl -s --key /etc/foreman/client_key.pem ' \ '--cert /etc/foreman/client_cert.pem ' \ f'{proxy[1]}/v2/features' self.add_cmd_output(_cmd, suggest_filename=proxy[0], subdir='smart_proxies_features', timeout=10) # collect http[|s]_proxy env.variables self.add_env_var(["http_proxy", "https_proxy"]) def build_query_cmd(self, query, csv=False, binary="psql"): """ Builds the command needed to invoke the pgsql query as the postgres user. The query requires significant quoting work to satisfy both the shell and postgres parsing requirements. Note that this will generate a large amount of quoting in sos logs referencing the command being run """ if csv: query = f"COPY ({query}) TO STDOUT " \ "WITH (FORMAT 'csv', DELIMITER ',', HEADER)" _dbcmd = "%s --no-password -h %s -p 5432 -U foreman -d foreman -c %s" return _dbcmd % (binary, self.dbhost, quote(query)) def postproc(self): self.do_path_regex_sub( r"/etc/foreman/(.*)((conf)(.*)?)", r"((\:|\s*)(passw|cred|token|secret|key).*(\:\s|=))(.*)", r"\1********") # yaml values should be alphanumeric self.do_path_regex_sub( r"/etc/foreman/(.*)((yaml|yml)(.*)?)", r"((\:|\s*)(passw|cred|token|secret|key).*(\:\s|=))(.*)", r'\1"********"') # Let the base Foreman class handle the string substitution of the apachepkg # attr so we can keep all log definitions centralized in the main class class RedHatForeman(Foreman, RedHatPlugin): apachepkg = 'httpd' def setup(self): self.add_file_tags({ '/usr/share/foreman/.ssh/ssh_config': 'ssh_foreman_config', }) super().setup() self.add_cmd_output('gem list') class DebianForeman(Foreman, DebianPlugin, UbuntuPlugin): apachepkg = 'apache2' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/snmp.py0000664000175000017500000000160714660147624016122 0ustar arifarif# Copyright (C) 2007 Sadique Puthen # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Snmp(Plugin): short_desc = 'Simple network management protocol' plugin_name = "snmp" profiles = ('system', 'sysmgmt') files = ('/etc/snmp/snmpd.conf',) def setup(self): self.add_copy_spec("/etc/snmp") class RedHatSnmp(Snmp, RedHatPlugin): packages = ('net-snmp',) class DebianSnmp(Snmp, DebianPlugin, UbuntuPlugin): packages = ('snmp',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovn_central.py0000664000175000017500000002276514660147624017467 0ustar arifarif# Copyright (C) 2018 Mark Michelson # Copyright (C) 2024 Alan Baghumian # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import os import re from sos.report.plugins import ( Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, ) class OVNCentral(Plugin): short_desc = 'OVN Northd' plugin_name = "ovn_central" profiles = ('network', 'virt') containers = ('ovn-dbs-bundle.*', 'ovn_cluster_north_db_server') container_name = "" ovn_nbdb_socket = "" ovn_sbdb_socket = "" ovn_socket = "" ovn_controller_sock_regex = "" ovn_northd_sock_regex = "" pfx = "" def _find_sock(self, path, regex_name): _sfile = self.path_join(path, regex_name) if self.container_name: res = self.exec_cmd(f"ls {path}", container=self.container_name) if res['status'] != 0 or '\n' not in res['output']: self._log_error( "Could not retrieve ovn_controller socket path " f"from container {self.container_name}" ) else: pattern = re.compile(regex_name) for filename in res['output'].split('\n'): if pattern.match(filename): return self.path_join(path, filename) # File not found, return the regex full path return _sfile def get_tables_from_schema(self, filename, skip=[]): """ Get tables from schema """ if self.container_name: cmd = f"cat {filename}" res = self.exec_cmd(cmd, timeout=None, foreground=True, container=self.container_name) if res['status'] != 0: self._log_error("Could not retrieve DB schema file from " f"container {self.container_name}") return None try: db_schema = json.loads(res['output']) except Exception: # pylint: disable=broad-except self._log_error(f"Cannot parse JSON file {filename}") return None else: try: fname = self.path_join(filename) with open(fname, 'r', encoding='UTF-8') as file: try: db_schema = json.load(file) except Exception: # pylint: disable=broad-except self._log_error(f"Cannot parse JSON file {filename}") return None except IOError as ex: self._log_error( f"Could not open DB schema file {filename}: {ex}") return None try: return [table for table in dict.keys( db_schema['tables']) if table not in skip] except AttributeError: self._log_error(f"DB schema {filename} has no 'tables' key") return None def add_database_output(self, tables, ovn_cmd): """ Collect OVN database output """ if tables: return [f"{ovn_cmd} list {table}" for table in tables] return None def setup(self): # check if env is a clustered or non-clustered one if self.container_exists(self.containers[1]): self.container_name = self.get_container_by_name( self.containers[1]) else: self.container_name = self.get_container_by_name( self.containers[0]) ovs_rundir = os.environ.get('OVS_RUNDIR') for pidfile in ['ovnnb_db.pid', 'ovnsb_db.pid', 'ovn-northd.pid']: self.add_copy_spec([ self.path_join('/var/lib/openvswitch/ovn', pidfile), self.path_join('/usr/local/var/run/openvswitch', pidfile), self.path_join('/run/openvswitch/', pidfile), self.path_join('/var/snap/microovn/common/run/ovn', pidfile), ]) if ovs_rundir: self.add_copy_spec(self.path_join(ovs_rundir, pidfile)) if self.get_option("all_logs"): self.add_copy_spec("/var/log/ovn/") else: self.add_copy_spec("/var/log/ovn/*.log") ovn_controller_socket = self._find_sock( self.ovn_socket, self.ovn_controller_sock_regex) northd_socket = self._find_sock(self.ovn_socket, self.ovn_northd_sock_regex) # ovsdb nb/sb cluster status commands cs = "cluster/status" cmds = [] pfx = self.pfx appctl_cmds = [ f"{pfx}ovs-appctl -t {self.ovn_nbdb_socket} {cs} OVN_Northbound", f"{pfx}ovs-appctl -t {self.ovn_sbdb_socket} {cs} OVN_Southbound", f"{pfx}ovn-appctl -t {northd_socket} status", f"{pfx}ovn-appctl -t {ovn_controller_socket} connection-status", ] self.add_cmd_output(appctl_cmds, foreground=True, container=self.container_name, timeout=30) # MicroOVN currently does not support this if not pfx: dfl = "debug/chassis-features-list" self.add_cmd_output(f"{pfx}ovn-appctl -t {northd_socket} {dfl}", foreground=True, container=self.container_name, timeout=30) # Some user-friendly versions of DB output nolo = "--no-leader-only" nbctl_cmds = [ f"{pfx}ovn-nbctl {nolo} show", f"{pfx}ovn-nbctl {nolo} get-ssl", f"{pfx}ovn-nbctl {nolo} get-connection", ] self.add_cmd_output(nbctl_cmds, foreground=True, container=self.container_name, timeout=30) sbctl_cmds = [ f"{pfx}ovn-sbctl {nolo} show", f"{pfx}ovn-sbctl {nolo} lflow-list", f"{pfx}ovn-sbctl {nolo} get-ssl", f"{pfx}ovn-sbctl {nolo} get-connection", ] self.add_cmd_output(sbctl_cmds, foreground=True, container=self.container_name, timeout=30) # backward compatibility for path in ['/usr/share/openvswitch', '/usr/share/ovn', '/snap/microovn/current/share/ovn']: if self.path_exists(self.path_join(path, 'ovn-nb.ovsschema')): nb_tables = self.get_tables_from_schema(self.path_join( path, 'ovn-nb.ovsschema')) cmds.extend(self.add_database_output(nb_tables, f"{pfx}ovn-nbctl {nolo}")) for path in ['/usr/share/openvswitch', '/usr/share/ovn', '/snap/microovn/current/share/ovn']: if self.path_exists(self.path_join(path, 'ovn-sb.ovsschema')): sb_tables = self.get_tables_from_schema(self.path_join( path, 'ovn-sb.ovsschema'), ['Logical_Flow']) cmds.extend(self.add_database_output(sb_tables, f"{pfx}ovn-sbctl {nolo}")) # If OVN is containerized, we need to run the above commands inside # the container. Removing duplicates (in case there are) to avoid # failing on collecting output to file on container running commands cmds = list(set(cmds)) self.add_cmd_output( cmds, foreground=True, container=self.container_name ) self.add_copy_spec("/etc/sysconfig/ovn-northd") ovs_dbdir = os.environ.get('OVS_DBDIR') for dbfile in ["ovnnb_db.db", "ovnsb_db.db"]: for path in [ "/var/lib/openvswitch/ovn", "/usr/local/etc/openvswitch", "/etc/openvswitch", "/var/lib/openvswitch", "/var/lib/ovn/etc", "/var/lib/ovn", "/var/snap/microovn/common/data/central/db", ]: dbfilepath = self.path_join(path, dbfile) if self.path_exists(dbfilepath): self.add_copy_spec(dbfilepath) self.add_dir_listing(dbfilepath) if ovs_dbdir: self.add_copy_spec(self.path_join(ovs_dbdir, dbfile)) self.add_journal(units="ovn-northd") class RedHatOVNCentral(OVNCentral, RedHatPlugin): packages = ('openvswitch-ovn-central', 'ovn.*-central', ) ovn_nbdb_socket = '/var/run/openvswitch/ovnnb_db.ctl' ovn_sbdb_socket = '/var/run/openvswitch/ovnsb_db.ctl' ovn_socket = '/var/run/openvswitch' ovn_controller_sock_regex = 'ovn-controller.*.ctl' ovn_northd_sock_regex = 'ovn-northd.*.ctl' class DebianOVNCentral(OVNCentral, DebianPlugin, UbuntuPlugin): packages = ('ovn-central', 'microovn', ) def setup(self): if self.path_exists('/snap/bin/microovn'): self.ovn_socket = '/var/snap/microovn/common/run/ovn' self.ovn_nbdb_socket = f"{self.ovn_socket}/ovnnb_db.ctl" self.ovn_sbdb_socket = f"{self.ovn_socket}/ovnsb_db.ctl" self.pfx = 'microovn.' else: self.ovn_socket = '/var/run/ovn' self.ovn_nbdb_socket = '/var/run/ovn/ovnnb_db.ctl' self.ovn_sbdb_socket = '/var/run/ovn/ovnsb_db.ctl' super().setup() ovn_controller_sock_regex = 'ovn-controller.*.ctl' ovn_northd_sock_regex = 'ovn-northd.*.ctl' sos-4.8.0/sos/report/plugins/zfs.py0000664000175000017500000000364114660147624015747 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Zfs(Plugin, IndependentPlugin): short_desc = 'ZFS filesystem' plugin_name = 'zfs' profiles = ('storage',) packages = ('zfsutils-linux', 'zfs',) def setup(self): self.add_cmd_output([ "zfs get all", "zfs list -t all -o space", "zpool list", "zpool events -v", "zpool status -vx" ]) self.add_copy_spec([ "/proc/spl/kmem/slab", "/proc/spl/kstat/zfs/fm", "/proc/spl/kstat/zfs/zil", "/proc/spl/kstat/zfs/dbufs", "/proc/spl/kstat/zfs/dbgmsg", "/proc/spl/kstat/zfs/dmu_tx", "/proc/spl/kstat/zfs/abdstats", "/proc/spl/kstat/zfs/arcstats", "/proc/spl/kstat/zfs/dbufstats", "/proc/spl/kstat/zfs/dnodestats", "/proc/spl/kstat/zfs/xuio_stats", "/proc/spl/kstat/zfs/zfetchstats", "/proc/spl/kstat/zfs/import_progress", "/proc/spl/kstat/zfs/fletcher_4_bench", "/proc/spl/kstat/zfs/vdev_cache_stats", "/proc/spl/kstat/zfs/vdev_raidz_bench", "/proc/spl/kstat/zfs/vdev_mirror_stats", "/proc/spl/taskq", "/proc/spl/taskq-all", ]) zpools = self.collect_cmd_output("zpool list -H -o name") if zpools['status'] == 0: zpools_list = zpools['output'].splitlines() for zpool in zpools_list: self.add_cmd_output(f"zpool get all {zpool}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/squid.py0000664000175000017500000000243214660147624016267 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Squid(Plugin): short_desc = 'Squid caching proxy' plugin_name = 'squid' profiles = ('webserver', 'services', 'sysmgmt') class RedHatSquid(Squid, RedHatPlugin): files = ('/etc/squid/squid.conf',) packages = ('squid',) def setup(self): self.add_copy_spec([ "/etc/squid/squid.conf", "/var/log/squid/access.log*", "/var/log/squid/cache.log*", "/var/log/squid/squid.out*" ]) class DebianSquid(Squid, DebianPlugin, UbuntuPlugin): plugin_name = 'squid' files = ('/etc/squid3/squid.conf',) packages = ('squid3',) def setup(self): self.add_copy_spec("/etc/squid3/squid.conf") self.add_copy_spec("/var/log/squid3/*") self.add_copy_spec('/etc/squid-deb-proxy') self.add_copy_spec("/var/log/squid-deb-proxy/*") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_novajoin.py0000664000175000017500000000223014660147624021030 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., David Vallee Delisle # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OpenStackNovajoin(Plugin): short_desc = 'OpenStack Novajoin' plugin_name = "openstack_novajoin" profiles = ('openstack', 'openstack_undercloud') def setup(self): self.add_copy_spec("/etc/novajoin/") if self.get_option("all_logs"): self.add_copy_spec("/var/log/novajoin/") else: self.add_copy_spec("/var/log/novajoin/*.log") def postproc(self): regexp = r"(password|memcache_secret_key)=(.*)" self.do_file_sub("/etc/novajoin/join.conf", regexp, r"\1=*********") class RedHatNovajoin(OpenStackNovajoin, RedHatPlugin): packages = ('python-novajoin',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/apt.py0000664000175000017500000000371614660147624015734 0ustar arifarif# Copyright (C) 2013 Louis Bouchard # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin, DebianPlugin class Apt(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'APT - advanced packaging tool' plugin_name = 'apt' profiles = ('system', 'sysmgmt', 'packagemanager') def setup(self): self.add_copy_spec([ "/etc/apt", "/var/log/apt", "/var/log/unattended-upgrades" ]) self.add_forbidden_path("/etc/apt/auth.conf") self.add_forbidden_path("/etc/apt/auth.conf.d/") self.add_cmd_output([ "apt-get check", "apt-config dump", "apt-cache stats", "apt-cache policy" ]) dpkg_result = self.exec_cmd( "dpkg-query -W -f='${binary:Package}\t${status}\n'" ) dpkg_output = dpkg_result['output'].splitlines() pkg_list = ' '.join( [v.split('\t')[0] for v in dpkg_output if 'ok installed' in v]) self.add_cmd_output( f"apt-cache policy {pkg_list}", suggest_filename="apt-cache_policy_details" ) def postproc(self): super().postproc() common_regex = r"(http(s)?://)\S+:\S+(@.*)" common_replace = r"\1******:******\3" files_to_sub = [ "/etc/apt/sources.list", "/etc/apt/sources.list.d/", "/etc/apt/apt.conf", "/etc/apt/apt.conf.d/", ] for file in files_to_sub: self.do_path_regex_sub( file, common_regex, common_replace ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ultrapath.py0000664000175000017500000000245714660147624017155 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class UltraPath(Plugin, RedHatPlugin): short_desc = 'HUAWEI UltraPath' plugin_name = 'ultrapath' profiles = ('storage', 'hardware') packages = ('UltraPath',) kernel_mods = ('nxup', 'nxupext_a') def setup(self): """ Huawei UltraPath specific information - commands """ self.add_cmd_output([ "upadm show version", "upadm show connectarray", "upadm show option", "upadm show upconfig", "upadm show diskarray", "upadmin show vlun", ]) result = self.collect_cmd_output('upadm show path') if result['status'] == 0: for line in result['output'].splitlines(): if line.startswith("Array ID :"): self.add_cmd_output(f"upadm show lun " f"array={line.split(':')[1].strip()}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/fapolicyd.py0000664000175000017500000000232614660147624017116 0ustar arifarif# Copyright (C) 2022 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Fapolicyd(Plugin, RedHatPlugin): """ This plugin collects configuration and some probes of Fapolicyd software framework. """ short_desc = 'Fapolicyd framework' plugin_name = "fapolicyd" packages = ("fapolicyd", ) def setup(self): self.add_copy_spec([ "/etc/fapolicyd/fapolicyd.conf", "/etc/fapolicyd/compiled.rules", "/etc/fapolicyd/fapolicyd.trust", "/etc/fapolicyd/rules.d/", "/etc/fapolicyd/trust.d/", "/var/log/fapolicyd-access.log", ]) self.add_cmd_output([ "fapolicyd-cli --list", "fapolicyd-cli --check-config", "fapolicyd-cli --check-trustdb", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/firewall_tables.py0000664000175000017500000001210314660147624020275 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, IndependentPlugin, SoSPredicate) class FirewallTables(Plugin, IndependentPlugin): """Collects information about local firewall tables, such as iptables, and nf_tables (via nft). Note that this plugin does _not_ collect firewalld information, which is handled by a separate plugin. Collections from this plugin are largely gated byt the presence of relevant kernel modules - for example, the plugin will not collect the nf_tables ruleset if both the `nf_tables` and `nfnetlink` kernel modules are not currently loaded (unless using the --allow-system-changes option). """ short_desc = 'firewall tables' plugin_name = "firewall_tables" profiles = ('network', 'system') files = ('/etc/nftables',) kernel_mods = ('ip_tables', 'ip6_tables', 'nf_tables', 'nfnetlink', 'ebtables') def collect_iptable(self, tablename): """ Collecting iptables rules for a table loads either kernel module of the table name (for kernel <= 3), or nf_tables (for kernel >= 4). If neither module is present, the rules must be empty.""" modname = "iptable_" + tablename cmd = "iptables -t " + tablename + " -nvL" self.add_cmd_output( cmd, pred=SoSPredicate(self, kmods=[modname, 'nf_tables'])) def collect_ip6table(self, tablename): """ Same as function above, but for ipv6 """ modname = "ip6table_" + tablename cmd = "ip6tables -t " + tablename + " -nvL" self.add_cmd_output( cmd, pred=SoSPredicate(self, kmods=[modname, 'nf_tables'])) def collect_nftables(self): """ Collects nftables rulesets with 'nft' commands if the modules are present """ # collect nftables ruleset nft_pred = SoSPredicate(self, kmods=['nf_tables', 'nfnetlink'], required={'kmods': 'all'}) return self.collect_cmd_output("nft -a list ruleset", pred=nft_pred, changes=True) def setup(self): # first, collect "nft list ruleset" as collecting commands like # ip6tables -t mangle -nvL # depends on its output # store in nft_ip_tables lists of ip[|6] tables from nft list nft_list = self.collect_nftables() nft_ip_tables = {'ip': [], 'ip6': []} nft_lines = nft_list['output'] if nft_list['status'] == 0 else '' for line in nft_lines.splitlines(): words = line.split()[0:3] if len(words) == 3 and words[0] == 'table' and \ words[1] in nft_ip_tables: nft_ip_tables[words[1]].append(words[2]) # collect iptables -t for any existing table, if we can't read the # tables, collect 2 default ones (mangle, filter) # do collect them only when relevant nft list ruleset exists default_ip_tables = "mangle\nfilter\nnat\n" try: proc_net_ip_tables = '/proc/net/ip_tables_names' with open(proc_net_ip_tables, 'r', encoding='UTF-8') as ifile: ip_tables_names = ifile.read() except IOError: ip_tables_names = default_ip_tables for table in ip_tables_names.splitlines(): if nft_list['status'] == 0 and table in nft_ip_tables['ip']: self.collect_iptable(table) # collect the same for ip6tables try: proc_net_ip6_tables = '/proc/net/ip6_tables_names' with open(proc_net_ip6_tables, 'r', encoding='UTF-8') as ipfile: ip_tables_names = ipfile.read() except IOError: ip_tables_names = default_ip_tables for table in ip_tables_names.splitlines(): if nft_list['status'] == 0 and table in nft_ip_tables['ip6']: self.collect_ip6table(table) # When iptables is called it will load: # 1) the modules iptables_filter (for kernel <= 3) or # nf_tables (for kernel >= 4) if they are not loaded. # 2) nft 'ip filter' table will be created # The same goes for ipv6. if nft_list['status'] != 0 or 'filter' in nft_ip_tables['ip']: self.add_cmd_output( "iptables -vnxL", pred=SoSPredicate(self, kmods=['iptable_filter', 'nf_tables']) ) if nft_list['status'] != 0 or 'filter' in nft_ip_tables['ip6']: self.add_cmd_output( "ip6tables -vnxL", pred=SoSPredicate(self, kmods=['ip6table_filter', 'nf_tables']) ) self.add_copy_spec([ "/etc/nftables", "/etc/sysconfig/nftables.conf", "/etc/nftables.conf", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ufw.py0000664000175000017500000000173314660147624015746 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, IndependentPlugin, SoSPredicate) class Ufw(Plugin, IndependentPlugin): short_desc = 'Uncomplicated FireWall' plugin_name = 'ufw' profiles = ('system', 'network') packages = ('ufw',) def setup(self): self.add_copy_spec([ "/etc/ufw", "/var/log/ufw.Log" ]) ufw_pred = SoSPredicate(self, kmods=['bpfilter', 'iptable_filter'], required={'kmods': 'all'}) self.add_cmd_output([ "ufw status numbered", "ufw app list" ], pred=ufw_pred) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/collectd.py0000664000175000017500000000436714660147624016744 0ustar arifarif# Copyright (C) 2016 Archit Sharma # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin class Collectd(Plugin, IndependentPlugin): short_desc = 'Collectd config collector' plugin_name = "collectd" profiles = ('services', 'webserver') # enable the plugin either when collectd package is installed # or being inside Super Proviledged Container that does not have # the package but logs to the host's logfile packages = ('collectd',) files = ('/var/log/containers/collectd/collectd.log', '/var/log/collectd/collectd.log') def setup(self): self.add_copy_spec([ '/etc/collectd.conf', '/etc/collectd.d/*.conf', '/var/log/containers/collectd/collectd.log', '/var/lib/config-data/puppet-generated/collectd/etc/collectd.conf', '/var/lib/config-data/puppet-generated/collectd/etc/collectd.d/' + '*.conf', ]) plugin = re.compile('^LoadPlugin.*') try: cfile = self.path_join("/etc/collectd.conf") with open(cfile, 'r', encoding='UTF-8') as file: for line in file: if plugin.match(line): self.add_alert("Active Plugin found: " f"{line.split()[-1]}") except IOError as err: self._log_warn(f"could not open /etc/collectd.conf: {err}") def postproc(self): # add these to protect_keys if need be: # "Port", "[<]*Host", protect_keys = [ "Password", "User", "[<]*URL", "Address" ] regexp = fr"(^[#]*\s*({'|'.join(protect_keys)})\s* \s*)(.*)" self.do_path_regex_sub( "/etc/collectd.d/*.conf", regexp, r'\1"*********"' ) self.do_file_sub("/etc/collectd.conf", regexp, r'\1"*********"') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_ironic.py0000664000175000017500000002044314660147624020476 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Lee Yarwood # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackIronic(Plugin): short_desc = 'OpenStack Ironic' plugin_name = "openstack_ironic" profiles = ('openstack', 'openstack_undercloud') containers = ('.*ironic_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/ironic" ins_puppet_gen = var_puppet_gen + "_inspector" conf_list = [] osc_available = False def setup(self): in_container = self.container_exists('.*ironic_api') if in_container: self.conf_list = [ self.var_puppet_gen + "/etc/ironic/*", self.var_puppet_gen + "/etc/ironic-inspector/*", self.var_puppet_gen + "_api/etc/ironic/*", self.ins_puppet_gen + "/etc/ironic-inspector/*", self.ins_puppet_gen + "/var/lib/httpboot/inspector.ipxe" ] self.add_copy_spec([ "/var/lib/ironic-inspector/", "/var/log/containers/ironic-inspector/ramdisk/", self.var_puppet_gen + "/etc/xinetd.conf", self.var_puppet_gen + "/etc/xinetd.d/", self.var_puppet_gen + "/etc/ironic/", self.var_puppet_gen + "/etc/ironic-inspector/", self.var_puppet_gen + "/etc/httpd/conf/", self.var_puppet_gen + "/etc/httpd/conf.d/", self.var_puppet_gen + "/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf", self.var_puppet_gen + "_api/etc/ironic/", self.var_puppet_gen + "_api/etc/httpd/conf/", self.var_puppet_gen + "_api/etc/httpd/conf.d/", self.var_puppet_gen + "_api/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "_api/etc/my.cnf.d/tripleo.cnf", self.ins_puppet_gen + "/etc/ironic-inspector/*", self.ins_puppet_gen + "/var/lib/httpboot/inspector.ipxe" ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/containers/ironic/", "/var/log/containers/ironic-inspector/" ]) else: self.add_copy_spec([ "/var/log/containers/ironic/*.log", "/var/log/containers/ironic-inspector/*.log", ]) for path in ['/var/lib/ironic', '/httpboot', '/tftpboot', self.ins_puppet_gen + '/var/lib/httpboot/', self.ins_puppet_gen + '/var/lib/tftpboot/']: self.add_dir_listing([ path, f"{self.var_puppet_gen}{path}" ], recursive=True) # Let's get the packages from the containers, always helpful when # troubleshooting. for container_name in ['ironic_inspector_dnsmasq', 'ironic_inspector', 'ironic_pxe_http', 'ironic_pxe_tftp', 'ironic_neutron_agent', 'ironic_conductor', 'ironic_api']: if self.container_exists('.*' + container_name): self.add_cmd_output('rpm -qa', container=container_name) else: self.conf_list = [ "/etc/ironic/*", "/etc/ironic-inspector/*", ] self.add_copy_spec([ "/etc/ironic/", "/etc/ironic-inspector/", "/var/lib/ironic-inspector/", "/var/log/ironic-inspector/ramdisk/", "/etc/my.cnf.d/tripleo.cnf", "/var/lib/httpboot/inspector.ipxe" ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/ironic/", "/var/log/ironic-inspector/", ]) else: self.add_copy_spec([ "/var/log/ironic/*.log", "/var/log/ironic-inspector/*.log", ]) self.add_dir_listing(['/var/lib/ironic', '/httpboot', '/tftpboot'], recursive=True) self.add_file_tags({ ".*/etc/ironic/ironic.conf": "ironic_conf" }) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] self.osc_available = all(vars_all) and any(vars_any) if not self.osc_available: self.soslog.warning("Not all environment variables set. Source " "the environment file for the user intended " "to connect to the OpenStack environment.") else: self.add_cmd_output("openstack baremetal driver list --long") self.add_cmd_output("openstack baremetal node list --long") self.add_cmd_output("openstack baremetal port list --long") self.add_cmd_output("openstack baremetal port group list --long") def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ for conf in self.conf_list: self.do_path_regex_sub(conf, regexp, subst) def postproc(self): protect_keys = [ "dns_passkey", "memcache_secret_key", "rabbit_password", "password", "qpid_password", "admin_password", "ssl_key_password", "os_password", "transport_url" ] connection_keys = ["connection", "sql_connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianIronic(OpenStackIronic, DebianPlugin, UbuntuPlugin): packages = ('ironic-api', 'ironic-common', 'ironic-conductor') class RedHatIronic(OpenStackIronic, RedHatPlugin): packages = ('openstack-selinux',) discoverd_packages = [ 'openstack-ironic-discoverd', 'openstack-ironic-discoverd-ramdisk' ] def collect_introspection_data(self): """ Capture baremetal introspection data """ uuids_result = self.collect_cmd_output( 'openstack baremetal node list -f value -c UUID' ) if uuids_result['status']: self.soslog.warning('Failed to fetch list of ironic node UUIDs, ' 'introspection data won\'t be collected') return uuids = [uuid for uuid in uuids_result['output'].split() if uuid.strip()] for uuid in uuids: self.add_cmd_output('openstack baremetal introspection ' f'data save {uuid}') def setup(self): super().setup() # ironic-discoverd was renamed to ironic-inspector in Liberty # is the optional ironic-discoverd service installed? if any(self.is_installed(p) for p in self.discoverd_packages): self.conf_list.append('/etc/ironic-discoverd/*') self.add_copy_spec('/etc/ironic-discoverd/') self.add_copy_spec('/var/lib/ironic-discoverd/') self.add_copy_spec('/var/log/ironic-discoverd/') self.add_journal(units="openstack-ironic-discoverd") self.add_journal(units="openstack-ironic-discoverd-dnsmasq") self.add_journal(units="openstack-ironic-inspector-dnsmasq") if self.osc_available: self.add_cmd_output("openstack baremetal introspection list") if self.get_option("all_logs"): self.collect_introspection_data() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/slurm.py0000664000175000017500000000740414660147624016310 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin, RedHatPlugin from sos.utilities import is_executable class Slurm(Plugin, UbuntuPlugin, RedHatPlugin): short_desc = "Slurm Workload Manager" plugin_name = 'slurm' profiles = ('hpc',) packages = ( # Ubuntu 'slurm-wlm', 'slurmd', 'slurmdbd', 'slurmctld', # EL 'slurm', 'slurm-slurmctld', 'slurm-slurmd', 'slurm-slurmdbd', ) services = ( 'slurmd', 'slurmdbd', 'slurmctld', ) def setup(self): """ Slurm Workload Manager """ self.add_copy_spec([ '/etc/slurm/*.conf', '/var/run/slurm/conf/*.conf', ]) if is_executable('sinfo'): self.add_cmd_output([ 'sinfo --all --list-reasons --long', 'sinfo --all --long', ]) if is_executable('squeue'): self.add_cmd_output([ 'squeue --all --long', ]) scontrol_cmds = [ 'aliases', 'assoc_mgr', 'bbstat', 'burstBuffer', 'config', 'daemons', 'dwstat', 'federation', 'frontend', 'job', 'licenses', 'node', 'partition', 'reservation', 'slurmd', 'step', 'topology', ] if is_executable('scontrol'): self.add_cmd_output( [f"scontrol show {i}" for i in scontrol_cmds] ) config_file = '/etc/slurm/slurm.conf' if not self.path_exists(config_file): config_file = '/var/run/slurm/conf/slurm.conf' slurmd_log_file = '/var/log/slurmd.log' slurmctld_log_file = '/var/log/slurmctld.log' try: with open(config_file, 'r', encoding='UTF-8') as cfile: for line in cfile.read().splitlines(): if not line: continue words = line.split('=') if words[0].strip() == 'SlurmdLogFile': slurmd_log_file = words[1].strip() if words[0].strip() == 'SlurmctldLogFile': slurmctld_log_file = words[1].strip() except IOError as error: self._log_error(f'Could not open conf file {config_file}:' f' {error}') if not self.get_option("all_logs"): self.add_copy_spec([ slurmd_log_file, slurmctld_log_file, ]) else: self.add_copy_spec([ f"{slurmd_log_file}*", f"{slurmctld_log_file}*", ]) def postproc(self): conf_paths = [ "/etc/slurm", "/var/run/slurm/conf", ] slurm_keys = [ 'AccountingStoragePass', 'JobCompPass', ] slurm_keys_regex = fr"(^\s*({'|'.join(slurm_keys)})\s*=\s*)(.*)" slurmdbd_key_regex = r'(^\s*(StoragePass)\s*=\s*)(.*)' sub = r'\1********' for conf_path in conf_paths: self.do_file_sub( f'{conf_path}/slurm.conf', slurm_keys_regex, sub ) self.do_file_sub( f'{conf_path}/slurmdbd.conf', slurmdbd_key_regex, sub ) sos-4.8.0/sos/report/plugins/openstack_trove.py0000664000175000017500000000440614660147624020353 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Lee Yarwood # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackTrove(Plugin): short_desc = 'OpenStack Trove' plugin_name = "openstack_trove" profiles = ('openstack', 'openstack_controller') var_puppet_gen = "/var/lib/config-data/puppet-generated/trove" def setup(self): if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/trove/", ]) else: self.add_copy_spec([ "/var/log/trove/*.log", ]) self.add_copy_spec([ '/etc/trove/', self.var_puppet_gen + '/etc/trove/' ]) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/trove/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/trove/*", regexp, subst ) def postproc(self): protect_keys = [ "default_password_length", "notifier_queue_password", "rabbit_password", "replication_password", "admin_password", "dns_passkey", "transport_url", "memcache_secret_key" ] connection_keys = ["connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianTrove(OpenStackTrove, DebianPlugin, UbuntuPlugin): packages = ( 'python-trove', 'trove-common', 'trove-api', 'trove-taskmanager', 'python3-trove', ) class RedHatTrove(OpenStackTrove, RedHatPlugin): packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ntb.py0000664000175000017500000000165114660147624015727 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Ntb(Plugin, RedHatPlugin): short_desc = 'Linux PCI-Express Non-Transparent Bridge' plugin_name = 'ntb' profiles = ('hardware', ) def setup(self): # NTB is hardwired at PCI Bus 0, device 3, function 0 on Intel # processors (see page 8 in # http://download.intel.com/design/intarch/papers/323328.pdf). self.add_copy_spec([ '/sys/kernel/debug/ntb_hw_intel/0000:*/info', '/sys/kernel/debug/ntb_transport/0000:*/qp*/stats' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/procenv.py0000664000175000017500000000130614660147624016615 0ustar arifarif# Copyright (c) 2012 Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, DebianPlugin, UbuntuPlugin class Procenv(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'Process environment' plugin_name = 'procenv' profiles = ('system',) def setup(self): self.add_cmd_output('procenv') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cman.py0000664000175000017500000000344014660147624016060 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, RedHatPlugin class Cman(Plugin, RedHatPlugin): short_desc = 'cman based Red Hat Cluster High Availability' plugin_name = "cman" profiles = ("cluster",) packages = ("luci", "cman", "clusterlib") files = ("/etc/cluster/cluster.conf",) def setup(self): self.add_copy_spec([ "/etc/cluster.conf", "/etc/cluster", "/etc/sysconfig/cluster", "/etc/sysconfig/cman", "/var/log/cluster", "/etc/fence_virt.conf", "/var/lib/luci/data/luci.db", "/var/lib/luci/etc", "/var/log/luci" ]) self.add_cmd_output([ "cman_tool services", "cman_tool nodes", "cman_tool status", "ccs_tool lsnode", "mkqdisk -L", "group_tool dump", "fence_tool dump", "fence_tool ls -n", "clustat", "rg_test test /etc/cluster/cluster.conf" ]) def postproc(self): for cluster_conf in glob("/etc/cluster/cluster.conf*"): self.do_file_sub( cluster_conf, r"(\s*\ # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os.path from sos.report.plugins import Plugin, RedHatPlugin # This plugin collects static configuration and runtime information # about OpenShift Origin based environments, like OpenShift Enterprise 3 # Some clarification on naming: # OpenShift Origin is the upstream project for OpenShift Enterprise, # OpenShift Container Platflorm, and Atomic Platform. # # However, the name "OpenShift Origin" refers to two different code bases: # * Origin M5 and later (https://github.com/openshift/origin) # which is upstream for OpenShift 3.x and later. # This is what this plugin handles # * Origin M4 and earlier (https://github.com/openshift/origin-server) # which is upstream for OpenShift 1.x and 2.x. # This is handled by the plugin in openshift.py # Note that this plugin should be used in conjunction with other plugins # in order to capture relevant data: the Kubernetes plugin for the # masters, the Docker plugin for the nodes, and also generic # plugins (e.g. for /etc/sysconfig entries, network setup etc) class OpenShiftOrigin(Plugin): short_desc = 'OpenShift Origin' plugin_name = "origin" files = None # file lists assigned after path setup below profiles = ('openshift',) master_base_dir = "/etc/origin/master" node_base_dir = "/etc/origin/node" master_cfg = os.path.join(master_base_dir, "master-config.yaml") master_env = os.path.join(master_base_dir, "master.env") node_cfg_file = "node-config.yaml" node_cfg = os.path.join(node_base_dir, node_cfg_file) node_kubeconfig = os.path.join(node_base_dir, "node.kubeconfig") static_pod_dir = os.path.join(node_base_dir, "pods") files = (master_cfg, node_cfg) # Master vs. node # # OpenShift Origin/3.x cluster members can be a master, a node, or both at # the same time: in most deployments masters are also nodes in order to get # access to the pod network, which some functionality (e.g. the API proxy) # requires. Therefore the following methods may all evaluate True on a # single instance (at least one must evaluate True if this is an OpenShift # installation) def is_master(self): """Determine if we are on a master""" return self.path_exists(self.master_cfg) def is_node(self): """Determine if we are on a node""" return self.path_exists(self.node_cfg) def is_static_etcd(self): """Determine if we are on a node running etcd""" return self.path_exists(self.path_join(self.static_pod_dir, "etcd.yaml")) def is_static_pod_compatible(self): """Determine if a node is running static pods""" return self.path_exists(self.static_pod_dir) def setup(self): bstrap_node_cfg = self.path_join(self.node_base_dir, "bootstrap-" + self.node_cfg_file) bstrap_kubeconfig = self.path_join(self.node_base_dir, "bootstrap.kubeconfig") node_certs = self.path_join(self.node_base_dir, "certs", "*") node_client_ca = self.path_join(self.node_base_dir, "client-ca.crt") admin_cfg = self.path_join(self.master_base_dir, "admin.kubeconfig") oc_cmd_admin = f"oc --config={admin_cfg}" static_pod_logs_cmd = "master-logs" # Note that a system can run both a master and a node. # See "Master vs. node" above. if self.is_master(): self.add_copy_spec([ self.master_cfg, self.master_env, self.path_join(self.master_base_dir, "*.crt"), ]) if self.is_static_pod_compatible(): self.add_copy_spec(self.path_join(self.static_pod_dir, "*.yaml")) self.add_cmd_output([ f"{static_pod_logs_cmd} api api", f"{static_pod_logs_cmd} controllers controllers", ]) if self.is_static_etcd(): self.add_cmd_output(f"{static_pod_logs_cmd} etcd etcd") # Possible enhancements: # some thoughts about information that might also be useful # to collect. However, these are maybe not needed in general # and/or present some challenges (scale, sensitive, ...) and need # some more thought. For now just leaving this comment here until # we decide if it's worth collecting: # # General project status: # oc status --all-namespaces (introduced in OSE 3.2) # -> deemed as not worthy in BZ#1394527 # Metrics deployment configurations # oc get -o json dc -n openshift-infra # Logging stack deployment configurations # oc get -o json dc -n logging # # Note: Information about nodes, events, pods, and services # is already collected by the Kubernetes plugin subcmds = [ "adm top images", "adm top imagestreams", "adm top nodes" ] self.add_cmd_output([ f'{oc_cmd_admin} {subcmd}' for subcmd in subcmds ]) jcmds = [ "hostsubnet", "clusternetwork", "netnamespaces" ] self.add_cmd_output([ f'{oc_cmd_admin} get -o json {jcmd}' for jcmd in jcmds ]) nmsps = [ 'default', 'openshift-web-console', 'openshift-ansible-service-broker', 'openshift-sdn', 'openshift-console' ] self.add_cmd_output([ f'{oc_cmd_admin} get -o json deploymentconfig,deployment,' f'daemonsets -n {n}' for n in nmsps ]) if not self.is_static_pod_compatible(): self.add_journal(units=["atomic-openshift-master", "atomic-openshift-master-api", "atomic-openshift-master-controllers"]) # get logs from the infrastructure pods running in the default ns pods = self.exec_cmd(f"{oc_cmd_admin} get pod -o name -n default") for pod in pods['output'].splitlines(): self.add_cmd_output(f"{oc_cmd_admin} logs -n default {pod}") # Note that a system can run both a master and a node. # See "Master vs. node" above. if self.is_node(): self.add_copy_spec([ self.node_cfg, self.node_kubeconfig, node_certs, node_client_ca, bstrap_node_cfg, bstrap_kubeconfig, self.path_join(self.node_base_dir, "*.crt"), self.path_join(self.node_base_dir, "resolv.conf"), self.path_join(self.node_base_dir, "node-dnsmasq.conf"), ]) self.add_journal(units="atomic-openshift-node") def postproc(self): # Clear env values from objects that can contain sensitive data # Sample JSON content: # { # "name": "MYSQL_PASSWORD", # "value": "mypassword" # }, # This will mask values when the "name" looks susceptible of # values worth obfuscating, i.e. if the name contains strings # like "pass", "pwd", "key" or "token". env_regexp = r'(?P{\s*"name":\s*[^,]*' \ r'(pass|pwd|key|token|cred|secret' \ r'|PASS|PWD|KEY|TOKEN|CRED|SECRET)[^,]*,' \ r'\s*"value":)[^}]*' self.do_cmd_output_sub('oc*json', env_regexp, r'\g "********"') # LDAP identity provider (bindPassword) # and github/google/OpenID identity providers (clientSecret) self.do_file_sub(self.master_cfg, r"(bindPassword|clientSecret):\s*(.*)", r'\1:"********"') class AtomicOpenShift(OpenShiftOrigin, RedHatPlugin): short_desc = 'OpenShift Enterprise / OpenShift Container Platform' packages = ('atomic-openshift',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/s390.py0000664000175000017500000000460114660147624015640 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Justin Payne # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class S390(Plugin, IndependentPlugin): short_desc = 'IBM S/390' plugin_name = 's390' profiles = ('system', 'hardware') architectures = ('s390.*',) def setup(self): self.add_copy_spec([ "/proc/cio_ignore", "/proc/crypto", "/proc/dasd/devices", "/proc/dasd/statistics", "/etc/dasd.conf", "/proc/qeth", "/proc/qeth_perf", "/proc/qeth_ipa_takeover", "/proc/sys/appldata/*", "/proc/sys/kernel/hz_timer", "/proc/sysinfo", "/sys/bus/ccwgroup/drivers/qeth/0.*/*", "/sys/bus/ccw/drivers/zfcp/0.*/*", "/sys/bus/ccw/drivers/zfcp/0.*/0x*/*", "/sys/bus/ccw/drivers/zfcp/0.*/0x*/0x*/*", "/sys/kernel/debug/s390dbf", "/etc/zipl.conf", "/etc/zfcp.conf", "/etc/sysconfig/dumpconf", "/etc/src_vipa.conf", "/etc/ccwgroup.conf", "/etc/chandev.conf", "/var/log/IBMtape.trace", "/var/log/IBMtape.errorlog", "/var/log/lin_tape.trace", "/var/log/lin_tape.errorlog", ]) # skip flush as it is useless for sos collection self.add_forbidden_path("/sys/kernel/debug/s390dbf/*/flush") self.add_cmd_output([ "lscss", "lsdasd", "lsshut", "lstape", "qethconf list_all", "lsqeth", "lszfcp", "lszfcp -D", "lszfcp -V", "lszcrypt -VV", "icainfo", "icastats", "smc_dbg" ]) ret = self.exec_cmd("ls /dev/dasd?") dasd_dev = ret['output'] for dev in dasd_dev.split('\n'): self.add_cmd_output([ f"dasdview -x -i -j -l -f {dev}", f"fdasd -p {dev}", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_instack.py0000664000175000017500000001375614660147624020660 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Lee Yarwood # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import configparser import os import re from sos.report.plugins import Plugin, RedHatPlugin NON_CONTAINERIZED_DEPLOY = [ '/home/stack/.instack/install-undercloud.log', '/home/stack/instackenv.json', '/home/stack/undercloud.conf' ] CONTAINERIZED_DEPLOY = [ '/var/log/heat-launcher/', '/home/stack/ansible.log', '/home/stack/config-download/', '/home/stack/install-undercloud.log', '/home/stack/undercloud-install-*.tar.bzip2', '/home/stack/.tripleo/history', '/var/lib/tripleo-config/', '/var/log/tripleo-container-image-prepare.log', ] UNDERCLOUD_CONF_PATH = '/home/stack/undercloud.conf' class OpenStackInstack(Plugin): short_desc = 'OpenStack Instack' plugin_name = "openstack_instack" profiles = ('openstack', 'openstack_undercloud') def setup(self): self.add_copy_spec(NON_CONTAINERIZED_DEPLOY + CONTAINERIZED_DEPLOY) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/zaqar/", ]) else: self.add_copy_spec([ "/var/log/zaqar/*.log", ]) self.add_file_tags({ "/var/log/mistral/executor.log": "mistral_executor_log" }) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] self.capture_undercloud_yamls() if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. Source " "the environment file for the user intended " "to connect to the OpenStack environment.") else: # capture all the possible stack uuids get_stacks = "openstack stack list" stacks = self.collect_cmd_output(get_stacks)['output'] stack_ids = re.findall(r'(\|\s(((\w+-){4})\w+)\s\|)', stacks) # get status of overcloud stack and resources for _sid in stack_ids: sid = _sid[1] self.add_cmd_output([ f"openstack stack show {sid}", f"openstack stack resource list -n 10 {sid}" ]) # get details on failed deployments cmd = f"openstack stack resource list -f value -n 5 {sid}" deployments = self.exec_cmd(cmd) for deployment in deployments['output'].splitlines(): if 'FAILED' in deployment: check = [ "OS::Heat::StructuredDeployment", "OS::Heat::SoftwareDeployment" ] if not any(x in deployment for x in check): continue deploy = deployment.split()[1] cmd = ("openstack software deployment " f"show --long {deployment}") fname = f"failed-deployment-{deploy}.log" self.add_cmd_output(cmd, suggest_filename=fname) self.add_cmd_output("openstack object save " "tripleo-ui-logs tripleo-ui.logs --file -") def capture_undercloud_yamls(self): """ capture yaml files to define overrides """ uc_config = configparser.ConfigParser() try: uc_config.read(UNDERCLOUD_CONF_PATH) override_opts = ['hieradata_override', 'net_config_override'] for opt in override_opts: path = uc_config.get('DEFAULT', opt) if path: if not os.path.isabs(path): path = self.path_join('/home/stack', path) self.add_copy_spec(path) except Exception: # pylint: disable=broad-except pass def postproc(self): # do_file_sub is case insensitive, so protected_keys can be lowercase # only protected_keys = [ "os_password", "undercloud_admin_password", "undercloud_ceilometer_metering_secret", "undercloud_ceilometer_password", "undercloud_ceilometer_snmpd_password", "undercloud_db_password", "undercloud_glance_password", "undercloud_heat_password", "undercloud_heat_stack_domain_admin_password", "undercloud_horizon_secret_key", "undercloud_ironic_password", "undercloud_neutron_password", "undercloud_nova_password", "undercloud_rabbit_password", "undercloud_swift_password", "undercloud_tuskar_password", ] regexp = fr"(({'|'.join(protected_keys)})=)(.*)" self.do_file_sub("/home/stack/.instack/install-undercloud.log", regexp, r"\1*********") self.do_file_sub(UNDERCLOUD_CONF_PATH, regexp, r"\1*********") protected_json_keys = ["pm_password", "ssh-key", "password"] json_regexp = fr'("({"|".join(protected_json_keys)})": )(".*?")' self.do_file_sub("/home/stack/instackenv.json", json_regexp, r"\1*********") self.do_file_sub('/home/stack/.tripleo/history', r'(password=)\w+', r'\1*********') class RedHatRDOManager(OpenStackInstack, RedHatPlugin): packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/microshift_ovn.py0000664000175000017500000000330614660147624020174 0ustar arifarif# Copyright (C) 2023 Pablo Acevedo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class MicroshiftOVN(Plugin, RedHatPlugin): """This plugin is used to collect MicroShift 4.x OVN logs. """ short_desc = 'MicroShift OVN' plugin_name = "microshift_ovn" plugin_timeout = 300 containers = ('ovnkube-node', 'ovnkube-master',) packages = ('microshift-networking',) profiles = ('microshift',) def setup(self): self.add_copy_spec([ '/etc/openvswitch/conf.db', '/etc/openvswitch/default.conf', '/etc/openvswitch/system-id.conf']) _ovs_cmd = 'ovs-appctl -t /var/run/ovn/' _subcmds = [ 'coverage/show', 'memory/show', 'ovsdb-server/sync-status' ] for file, dbn in [('ovnnb_db.ctl', 'OVN_Northbound'), ('ovnsb_db.ctl', 'OVN_Southbound')]: self.add_cmd_output( [f"{_ovs_cmd}{file} {cmd}" for cmd in _subcmds], timeout=MicroshiftOVN.plugin_timeout) self.add_cmd_output( f"{_ovs_cmd}{file} ovsdb-server/get-db-storage-status {dbn}", timeout=MicroshiftOVN.plugin_timeout) self.add_cmd_output( f'{_ovs_cmd}ovn-controller.*.ctl ct-zone-list', timeout=MicroshiftOVN.plugin_timeout) sos-4.8.0/sos/report/plugins/pci.py0000664000175000017500000000254614660147624015723 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Pci(Plugin, IndependentPlugin): short_desc = 'PCI devices' plugin_name = "pci" profiles = ('hardware', 'system') def check_for_bus_devices(self): """ Check whether PCI devices present """ if not self.path_isdir('/proc/bus/pci'): return False # ensure that more than just the 'devices' file, which can be empty, # exists in the pci directory. This implies actual devices are present content = self.listdir('/proc/bus/pci') if 'devices' in content: content.remove('devices') return len(content) > 0 def setup(self): self.add_copy_spec([ "/proc/ioports", "/proc/iomem", "/proc/bus/pci" ]) if self.check_for_bus_devices(): self.add_cmd_output("lspci -nnvv", root_symlink="lspci", tags="lspci") self.add_cmd_output("lspci -tv") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/xfs.py0000664000175000017500000000253514660147624015746 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Xfs(Plugin, IndependentPlugin): """This plugin collects information on mounted XFS filessystems on the local system. Users should expect `xfs_info` and `xfs_admin` collections by this plugin for each XFS filesystem that is locally mounted. """ short_desc = 'XFS filesystem' plugin_name = 'xfs' profiles = ('storage',) files = ('/sys/fs/xfs', '/proc/fs/xfs') kernel_mods = ('xfs',) def setup(self): mounts = '/proc/mounts' ext_fs_regex = r"^(/dev/.+).+xfs\s+" for dev in zip(self.do_regex_find_all(ext_fs_regex, mounts)): for ext in dev: parts = ext.split(' ') self.add_cmd_output(f"xfs_info {parts[1]}", tags="xfs_info") self.add_cmd_output(f"xfs_admin -l -u {parts[0]}") self.add_copy_spec([ '/proc/fs/xfs', '/sys/fs/xfs' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/qaucli.py0000664000175000017500000000254514660147624016425 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Qaucli(Plugin, IndependentPlugin): short_desc = 'QLogic information' plugin_name = 'qaucli' profiles = ('system', 'storage', 'hardware',) packages = ('QConvergeConsoleCLI',) def setup(self): cmd = "/opt/QLogic_Corporation/QConvergeConsoleCLI/qaucli" subcmds = ['-c', '-g', '-pr fc -z', '-t'] for subcmd in subcmds: self.add_cmd_output( f"{cmd} {subcmd}", suggest_filename=f"qaucli_{subcmd}") result = self.collect_cmd_output( f"{cmd} -i", suggest_filename="qaucli_-i") if result['status'] == 0: for line in result['output'].splitlines(): if "HBA Instance" in line: hba = line.split(':')[1].strip() self.add_cmd_output( f"{cmd} -l {hba}", suggest_filename=f"qaucli_-l_{hba}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sanlock.py0000664000175000017500000000175014660147624016576 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class SANLock(Plugin): short_desc = 'SANlock daemon' plugin_name = "sanlock" profiles = ('cluster', 'virt') packages = ("sanlock",) def setup(self): self.add_copy_spec("/var/log/sanlock.log*") self.add_cmd_output([ "sanlock client status -D", "sanlock client host_status -D", "sanlock client log_dump" ]) class RedHatSANLock(SANLock, RedHatPlugin): files = ("/etc/sysconfig/sanlock",) def setup(self): super().setup() self.add_copy_spec("/etc/sysconfig/sanlock") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sunbeam.py0000664000175000017500000001304614660147624016577 0ustar arifarif# Copyright (C) 2024 Canonical Ltd., Arif Ali # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import pwd from sos.report.plugins import Plugin, UbuntuPlugin, PluginOpt class Sunbeam(Plugin, UbuntuPlugin): short_desc = "Sunbeam Openstack Controller" plugin_name = "sunbeam" profiles = ('cloud',) packages = ('openstack',) common_dir = '/var/snap/openstack/common' option_list = [ PluginOpt('sunbeam-user', default='ubuntu', val_type=str, desc='The user used for sunbeam installation'), PluginOpt('juju-allow-login', default=False, val_type=bool, desc='Allow sos to login to juju'), ] def setup(self): self.add_service_status('snap.openstack.*') self.add_copy_spec([ f'{self.common_dir}/hooks.log', f'{self.common_dir}/state/daemon.yaml', f'{self.common_dir}/state/truststore/sunbeam.maas.yaml', f'{self.common_dir}/state/database/info.yaml', f'{self.common_dir}/state/database/cluster.yaml', '/var/snap/openstack/current/config.yaml', ]) self.add_cmd_output([ 'sunbeam cluster list', 'sunbeam cluster list --format yaml', ], snap_cmd=True) sunbeam_user = self.get_option("sunbeam-user") try: user_pwd = pwd.getpwnam(sunbeam_user) except KeyError: # The user doesn't exist, this will skip the rest self._log_warn( f'User "{sunbeam_user}" does not exist, will not collect juju ' 'information. Use `-k sunbeam.sunbeam-user` option to define ' 'the user to use to collect data for sunbeam') return if user_pwd: sb_snap_homedir = f'{user_pwd.pw_dir}/snap/openstack/common' self.add_copy_spec([ f"{sb_snap_homedir}/*.log", f"{sb_snap_homedir}/etc/*/*.log", f"{sb_snap_homedir}/logs/*.log", ]) if self.get_option("juju-allow-login"): self.exec_cmd( f'su - {sunbeam_user} -c "sunbeam utils juju-login"') # This checks if the juju user is logged in, and if it is, then we # collect the juju information. It could be that the user was # already logged in from a prior session juju_whoami = self.exec_cmd('juju whoami', runas=sunbeam_user) juju_status = self.exec_cmd('juju status', runas=sunbeam_user, timeout=30) logged_in = False if juju_whoami['status'] == 0 or juju_status['status'] == 0: try: j_whoami = juju_whoami['output'].splitlines()[0] j_status = juju_status['output'].splitlines()[0] if "Controller" in j_whoami or "Controller" in j_status: self._get_juju_cmd_details(sunbeam_user) logged_in = True except IndexError: # One of the commands may not have gone through and hence # not logged in pass if not logged_in: self._log_warn( "juju is not logged in, will not collect juju " "information. Use `-k sunbeam.juju-allow-login=True` to " "login or use `juju login` as the sunbeam user to " "login") def _get_juju_cmd_details(self, user): self.add_cmd_output("juju controllers", runas=user, snap_cmd=True) juju_controllers = self.collect_cmd_output( "juju controllers --format json", runas=user) if juju_controllers['status'] == 0: juju_ctrl_json = json.loads(juju_controllers['output']) for controller in juju_ctrl_json['controllers'].keys(): self.add_cmd_output([ f'juju models -c {controller}', f'juju model-defaults -c {controller}', f'juju controller-config -c {controller}', f'juju controller-config -c {controller} --format json', ], runas=user, snap_cmd=True) juju_models = self.collect_cmd_output( f'juju models -c {controller} --format json', runas=user) if juju_models['status'] == 0: juju_status_json = json.loads(juju_models['output']) for model in juju_status_json['models']: model_name = f'{controller}:{model["name"]}' self.add_cmd_output([ f'juju status -m {model_name}', f'juju status -m {model_name} --format json', f'juju model-config -m {model_name}', f'juju model-config -m {model_name} --format json', ], runas=user, snap_cmd=True) def postproc(self): self.do_file_private_sub( f'{self.common_dir}/state/truststore/sunbeam.maas.yaml' ) self.do_cmd_private_sub('juju controllers') self.do_cmd_private_sub('juju controller-config') # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/apparmor.py0000664000175000017500000000206614660147624016766 0ustar arifarif# Copyright (c) 2012 Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class Apparmor(Plugin, UbuntuPlugin): short_desc = 'Apparmor mandatory access control' plugin_name = 'apparmor' profiles = ('security',) def setup(self): self.add_copy_spec([ "/etc/apparmor*" ]) self.add_forbidden_path([ "/etc/apparmor.d/cache", "/etc/apparmor.d/libvirt/libvirt*", "/etc/apparmor.d/abstractions" ]) self.add_cmd_output("apparmor_status") self.add_dir_listing([ '/etc/apparmor.d/abstractions', '/etc/apparmor.d/libvirt' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/tpm2.py0000664000175000017500000000173514660147624016031 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class TPM2(Plugin, IndependentPlugin): """ Collects information about TPM2 module installed on host system. This plugin will capture data using tpm2_tools """ short_desc = 'Trusted Platform Module 2.0' plugin_name = 'tpm2' profiles = ('security', 'system', 'storage', 'hardware') packages = ('tpm2-tools',) def setup(self): self.add_cmd_output([ 'tpm2_getcap properties-variable', 'tpm2_getcap properties-fixed', 'tpm2_nvreadpublic', 'tpm2_readclock' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/convert2rhel.py0000664000175000017500000000204714660147624017561 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Convert2RHEL(Plugin, RedHatPlugin): """This plugin collects data generated by Convert2RHEL.""" short_desc = 'Convert2RHEL' plugin_name = 'convert2rhel' profiles = ('system',) packages = ('convert2rhel',) verify_packages = ('convert2rhel$',) def setup(self): self.add_copy_spec([ "/var/log/convert2rhel/convert2rhel.log", "/var/log/convert2rhel/archive/convert2rhel-*.log", "/var/log/convert2rhel/rpm_va.log", # Convert2RHEL generates a {pre,post}-conversion report. "/var/log/convert2rhel/convert2rhel-*-conversion.*", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/telegraf.py0000664000175000017500000000327214660147624016736 0ustar arifarif# Copyright (C) 2024 Marcin Wilk # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Telegraf(Plugin, IndependentPlugin): short_desc = 'Telegraf, the metric collecting tool, plugin' plugin_name = "telegraf" profiles = ('observability',) services = ('telegraf',) files = ( '/etc/telegraf/', '/etc/default/telegraf', ) def setup(self): # Collect data from 'files' var super().setup() # collect logs in addition to the above if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/telegraf/", ]) else: self.add_copy_spec([ "/var/log/telegraf/*.log", ]) def postproc(self): protect_keys = [ "password", "token", "pwd", ] telegraf_path_exps = [ "/etc/telegraf/*", "/etc/default/telegraf", ] match_exp = fr"(^\s*(.*({'|'.join(protect_keys)}))\s*=\s*)(.*)" # Obfuscate passwords and keys self.do_path_regex_sub(fr"({'|'.join(telegraf_path_exps)})", match_exp, r"\1*********") # Obfuscate certs self.do_file_private_sub(telegraf_path_exps[0]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/candlepin.py0000664000175000017500000001353114660147624017101 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from re import match from shlex import quote from sos.report.plugins import Plugin, RedHatPlugin class Candlepin(Plugin, RedHatPlugin): short_desc = 'Candlepin entitlement management' plugin_name = 'candlepin' packages = ('candlepin',) dbhost = None dbpasswd = None env = None def setup(self): # for external DB, search in /etc/candlepin/candlepin.conf for: # org.quartz.dataSource.myDS.URL=.. # # and for DB password, search for # org.quartz.dataSource.myDS.password=.. self.dbhost = "localhost" self.dbpasswd = "" cfg_file = "/etc/candlepin/candlepin.conf" try: with open(cfg_file, 'r', encoding='UTF--8') as cfile: candle_lines = cfile.read().splitlines() for line in candle_lines: # skip empty lines and lines with comments if not line or line[0] == '#': continue if match(r"^\s*org.quartz.dataSource.myDS.URL=\S+", line): self.dbhost = line.split('=')[1] # separate hostname from value like # jdbc:postgresql://localhost:5432/candlepin self.dbhost = self.dbhost.split('/')[2].split(':')[0] if match(r"^\s*org.quartz.dataSource.myDS.password=\S+", line): self.dbpasswd = line.split('=')[1] except (IOError, IndexError): # fallback when the cfg file is not accessible or parseable pass self.add_file_tags({ '/var/log/candlepin/candlepin.log.*': 'candlepin_log', '/var/log/candlepin/err.log.*': 'candlepin_error_log', '/etc/candlepin/candlepin.conf': 'candlepin_conf' }) # set the password to os.environ when calling psql commands to prevent # printing it in sos logs # we can't set os.environ directly now: other plugins can overwrite it self.env = {"PGPASSWORD": self.dbpasswd} # Always collect the full active log of these self.add_copy_spec([ "/var/log/candlepin/error.log", "/var/log/candlepin/candlepin.log" ], sizelimit=0) # Allow limiting on logrotated logs self.add_copy_spec([ "/etc/candlepin/candlepin.conf", "/etc/candlepin/broker.xml", "/var/log/candlepin/audit*.log*", "/var/log/candlepin/candlepin.log[.-]*", "/var/log/candlepin/cpdb*.log*", "/var/log/candlepin/cpinit*.log*", "/var/log/candlepin/error.log[.-]*", # Specific to candlepin, ALL catalina logs are relevant. Adding it # here rather than the tomcat plugin to ease maintenance and not # pollute non-candlepin sos reports that enable the tomcat plugin "/var/log/tomcat*/catalina*log*", "/var/log/tomcat*/host-manager*log*", "/var/log/tomcat*/localhost*log*", "/var/log/tomcat*/manager*log*", ]) self.add_cmd_output("du -sh /var/lib/candlepin/*/*") # collect tables sizes, ordered _cmd = self.build_query_cmd( "SELECT table_name, pg_size_pretty(total_bytes) AS total, " "pg_size_pretty(index_bytes) AS INDEX , " "pg_size_pretty(toast_bytes) AS toast, pg_size_pretty(table_bytes)" " AS TABLE FROM ( SELECT *, " "total_bytes-index_bytes-COALESCE(toast_bytes,0) AS table_bytes " "FROM (SELECT c.oid,nspname AS table_schema, relname AS " "TABLE_NAME, c.reltuples AS row_estimate, " "pg_total_relation_size(c.oid) AS total_bytes, " "pg_indexes_size(c.oid) AS index_bytes, " "pg_total_relation_size(reltoastrelid) AS toast_bytes " "FROM pg_class c LEFT JOIN pg_namespace n ON " "n.oid = c.relnamespace WHERE relkind = 'r') a) a order by " "total_bytes DESC" ) self.add_cmd_output(_cmd, suggest_filename='candlepin_db_tables_sizes', env=self.env) _cmd = self.build_query_cmd("\ SELECT displayname, content_access_mode \ FROM cp_owner;") self.add_cmd_output(_cmd, suggest_filename='simple_content_access', env=self.env) def build_query_cmd(self, query, csv=False): """ Builds the command needed to invoke the pgsql query as the postgres user. The query requires significant quoting work to satisfy both the shell and postgres parsing requirements. Note that this will generate a large amount of quoting in sos logs referencing the command being run """ csvformat = "-A -F , -X" if csv else "" _dbcmd = "psql --no-password -h %s -p 5432 -U candlepin \ -d candlepin %s -c %s" return _dbcmd % (self.dbhost, csvformat, quote(query)) def postproc(self): reg = r"(((.*)(pass|token|secret)(.*))=)(.*)" repl = r"\1********" self.do_file_sub("/etc/candlepin/candlepin.conf", reg, repl) cpdbreg = r"(--password=)([a-zA-Z0-9]*)" self.do_file_sub("/var/log/candlepin/cpdb.log", cpdbreg, repl) for key in ["trustStorePassword", "keyStorePassword"]: self.do_file_sub("/etc/candlepin/broker.xml", fr"({key})=(\w*)([;<])", r"\1=********\3") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/targetcli.py0000664000175000017500000000217414660147624017123 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class TargetCli(Plugin, IndependentPlugin): short_desc = 'TargetCLI TCM/LIO configuration' packages = ('targetcli', 'python-rtslib') profiles = ('storage', ) plugin_name = 'targetcli' def setup(self): self.add_cmd_output([ "targetcli ls", "targetcli status", ]) sys_conf_dir = '/sys/kernel/config/target' self.add_forbidden_path([ self.path_join(sys_conf_dir, '**/password*'), ]) self.add_service_status("target") self.add_journal(units="targetcli") self.add_copy_spec(sys_conf_dir) self.add_copy_spec("/etc/target") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cgroups.py0000664000175000017500000000255414660147624016631 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, CosPlugin) class Cgroups(Plugin, DebianPlugin, UbuntuPlugin, CosPlugin): short_desc = 'Control groups subsystem' plugin_name = "cgroups" profiles = ('container', 'system') files = ('/proc/cgroups',) def setup(self): self.add_file_tags({ '/proc/1/cgroups': 'init_process_cgroup' }) self.add_copy_spec([ "/proc/cgroups", "/sys/fs/cgroup" ]) self.add_cmd_output("systemd-cgls") self.add_forbidden_path( "/sys/fs/cgroup/memory/**/memory.kmem.slabinfo" ) class RedHatCgroups(Cgroups, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec([ "/etc/sysconfig/cgconfig", "/etc/sysconfig/cgred", "/etc/cgsnapshot_blacklist.conf", "/etc/cgconfig.conf", "/etc/cgrules.conf" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ruby.py0000664000175000017500000000145514660147624016127 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Ruby(Plugin, IndependentPlugin): short_desc = 'Ruby runtime' plugin_name = 'ruby' packages = ('ruby', 'ruby-irb') def setup(self): self.add_cmd_output([ 'ruby --version', 'irb --version', 'gem --version', 'gem list' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/watchdog.py0000664000175000017500000000562214660147624016746 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Reid Wahl # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Watchdog(Plugin, RedHatPlugin): short_desc = 'Watchdog information.' plugin_name = 'watchdog' profiles = ('system',) packages = ('watchdog',) option_list = [ PluginOpt('conf-file', default='/etc/watchdog.conf', desc='watchdog config file') ] def get_log_dir(self, conf_file): """Get watchdog log directory. Get watchdog log directory path configured in ``conf_file``. :returns: The watchdog log directory path. :returntype: str. :raises: IOError if ``conf_file`` is not readable. """ log_dir = None with open(conf_file, 'r', encoding='UTF-8') as conf_f: for line in conf_f: line = line.split('#')[0].strip() try: (key, value) = line.split('=', 1) if key.strip() == 'log-dir': log_dir = value.strip() except ValueError: # not a valid key, value line and we can safely ignore pass return log_dir def setup(self): """Collect watchdog information. Collect configuration files, custom executables for test-binary and repair-binary, and stdout/stderr logs. """ conf_file = self.path_join(self.get_option('conf-file')) log_dir = self.path_join('/var/log/watchdog') # Get service configuration and sysconfig files self.add_copy_spec([ conf_file, '/etc/sysconfig/watchdog', ]) # Get custom executables self.add_copy_spec([ '/etc/watchdog.d', '/usr/libexec/watchdog/scripts', ]) # Get logs try: res = self.get_log_dir(conf_file) if res: log_dir = res except IOError as ex: self._log_warn(f"Could not read {conf_file}: {ex}") if self.get_option('all_logs'): log_files = glob(self.path_join(log_dir, '*')) else: log_files = (glob(self.path_join(log_dir, '*.stdout')) + glob(self.path_join(log_dir, '*.stderr'))) self.add_copy_spec(log_files) # Get output of "wdctl " for each /dev/watchdog* for dev in glob(self.path_join('/dev/watchdog*')): self.add_cmd_output(f"wdctl {dev}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/pxe.py0000664000175000017500000000261714660147624015743 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Pxe(Plugin): short_desc = 'PXE service' plugin_name = "pxe" profiles = ('sysmgmt', 'network') option_list = [ PluginOpt('tftpboot', default=False, desc='collect content from tftpboot path') ] class RedHatPxe(Pxe, RedHatPlugin): files = ('/usr/sbin/pxeos',) packages = ('system-config-netboot-cmd',) def setup(self): super().setup() self.add_cmd_output("/usr/sbin/pxeos -l") self.add_copy_spec("/etc/dhcpd.conf") if self.get_option("tftpboot"): self.add_copy_spec("/tftpboot") class DebianPxe(Pxe, DebianPlugin, UbuntuPlugin): packages = ('tftpd-hpa',) def setup(self): super().setup() self.add_copy_spec([ "/etc/dhcp/dhcpd.conf", "/etc/default/tftpd-hpa" ]) if self.get_option("tftpboot"): self.add_copy_spec("/var/lib/tftpboot") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_manila.py0000664000175000017500000000727414660147624020463 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc.,Poornima M. Kshirsagar # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackManila(Plugin): short_desc = 'OpenStack Manila' plugin_name = "openstack_manila" profiles = ('openstack', 'openstack_controller') containers = ('.*manila_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/manila" def setup(self): pre_conf = ( self.var_puppet_gen if self.container_exists('.*manila_api') else '') config_dir = f"{pre_conf}/etc/manila" manila_cmd = f"manila-manage --config-dir {config_dir} db version" self.add_cmd_output(manila_cmd, suggest_filename="manila_db_version") self.add_copy_spec([ "/etc/manila/", self.var_puppet_gen + "/etc/manila/", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf", self.var_puppet_gen + "/etc/httpd/conf/", self.var_puppet_gen + "/etc/httpd/conf.d/", self.var_puppet_gen + "/etc/httpd/conf.modules.d/*.conf", ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/manila/*", ]) else: self.add_copy_spec([ "/var/log/manila/*.log", ]) self.add_file_tags({ ".*/etc/manila/manila.conf": "manila_conf" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/manila/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/manila/*", regexp, subst ) def postproc(self): protect_keys = [".*password.*", "transport_url", "hdfs_ssh_pw", "maprfs_ssh_pw", "memcache_secret_key"] connection_keys = ["connection", "sql_connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianManila(OpenStackManila, DebianPlugin, UbuntuPlugin): short_desc = 'OpenStack Manila information for Debian based distributions' packages = ( 'python-manila', 'manila-common', 'manila-api', 'manila-share', 'manila-scheduler', 'python3-manila', ) def setup(self): super().setup() if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/apache2/manila*", ]) else: self.add_copy_spec([ "/var/log/apache2/manila*.log", ]) class RedHatManila(OpenStackManila, RedHatPlugin): short_desc = 'OpenStack Manila information for Red Hat distributions' packages = ('openstack-selinux',) def setup(self): super().setup() self.add_copy_spec("/etc/sudoers.d/manila") if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/containers/manila/*" ]) else: self.add_copy_spec([ "/var/log/containers/manila/*.log" ]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/microshift.py0000664000175000017500000001572214660147624017317 0ustar arifarif# Copyright 2023 Red Hat, Inc. Pablo Acevedo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Microshift(Plugin, RedHatPlugin): """This is the plugin for MicroShift 4.X. Even though it shares some of the OpenShift components, its IoT/Edge target makes the product nimble and light, thus requiring different a approach when operating it. When enabled, this plugin will collect cluster information (such as systemd service logs, configuration, versions, etc.)and also inspect API resources in certain namespaces. The namespaces to scan are kube.* and openshift.*. Other namespaces may be collected by making use of the ``only-namespaces`` or ``add-namespaces`` options. """ short_desc = 'Microshift' plugin_name = 'microshift' plugin_timeout = 900 packages = ('microshift', 'microshift-selinux', 'microshift-networking',) services = (plugin_name,) profiles = (plugin_name,) localhost_kubeconfig = '/var/lib/microshift/resources/kubeadmin/kubeconfig' option_list = [ PluginOpt('kubeconfig', default=localhost_kubeconfig, val_type=str, desc='Path to a locally available kubeconfig file'), PluginOpt('only-namespaces', default='', val_type=str, desc='colon-delimited list of namespaces to collect from'), PluginOpt('add-namespaces', default='', val_type=str, desc=('colon-delimited list of namespaces to add to the ' 'default collection list')) ] def _setup_namespace_regexes(self): """Combine a set of regexes for collection with any namespaces passed to sos via the -k openshift.add-namespaces option. Note that this does allow for end users to specify namespace regexes of their own. """ if self.get_option('only-namespaces'): return list(self.get_option('only-namespaces').split(':')) collect_regexes = [ r'^openshift\-.+$', r'^kube\-.+$' ] if self.get_option('add-namespaces'): for nsp in self.get_option('add-namespaces').split(':'): collect_regexes.append(fr'^{nsp}$') return collect_regexes def _reduce_namespace_list(self, nsps): """Reduce the namespace listing returned to just the ones we want to collect from. By default, as requested by OCP support personnel, this must include all 'openshift' prefixed namespaces :param nsps list: Namespace names from oc output """ def _match_namespace(namespace, regexes): """Match a particular namespace for inclusion (or not) in the collection phases :param namespace str: The name of a namespace """ for regex in regexes: if re.match(regex, namespace): return True return False regexes = self._setup_namespace_regexes() return list(set(n for n in nsps if _match_namespace(n, regexes))) def _get_namespaces(self): res = self.exec_cmd( 'oc get namespaces' ' -o custom-columns=NAME:.metadata.name' ' --no-headers' f' --kubeconfig={self.get_option("kubeconfig")}') if res['status'] == 0: return self._reduce_namespace_list(res['output'].split('\n')) return [] def _get_cluster_resources(self): """Get cluster-level (non-namespaced) resources to collect """ global_resources = [ 'apiservices', 'certificatesigningrequests', 'clusterrolebindings', 'clusterroles', 'componentstatuses', 'csidrivers', 'csinodes', 'customresourcedefinitions', 'flowschemas', 'ingressclasses', 'logicalvolumes', 'mutatingwebhookconfigurations', 'nodes', 'persistentvolumes', 'priorityclasses', 'prioritylevelconfigurations', 'rangeallocations', 'runtimeclasses', 'securitycontextconstraints', 'selfsubjectaccessreviews', 'selfsubjectrulesreviews', 'storageclasses', 'subjectaccessreviews', 'tokenreviews', 'validatingwebhookconfigurations', 'volumeattachments' ] _filtered_resources = [] for resource in global_resources: res = self.exec_cmd( f"oc get --kubeconfig {self.get_option('kubeconfig')} " f"{resource}", timeout=Microshift.plugin_timeout) if res['status'] == 0: _filtered_resources.append(resource) return _filtered_resources def setup(self): """The setup() phase of this plugin will first gather system information and then iterate through all default namespaces, and/or those specified via the `add-namespaces` and `only-namespaces` plugin options. Both of these options accept shell-style regexes. Output format for this function is based on `oc adm inspect` command, which is used to retrieve all API resources from the cluster. """ self.add_journal('microshift-etcd.scope') self.add_copy_spec('/etc/microshift') if self.path_exists('/var/lib/microshift-backups'): self.add_copy_spec(['/var/lib/microshift-backups/*/version', '/var/lib/microshift-backups/*.json']) self.add_copy_spec(['/var/log/kube-apiserver/*.log']) self.add_cmd_output([ 'microshift version', 'microshift show-config -m effective' ]) _cluster_resources_to_collect = ",".join( self._get_cluster_resources()) _namespaces_to_collect = " ".join( [f'ns/{n}' for n in self._get_namespaces()]) if self.is_service_running(Microshift.plugin_name): _subdir = self.get_cmd_output_path(make=False) _kubeconfig = self.get_option('kubeconfig') self.add_cmd_output( f'oc adm inspect --kubeconfig {_kubeconfig} --dest-dir ' f'{_subdir} {_cluster_resources_to_collect}', suggest_filename='inspect_cluster_resources.log', timeout=Microshift.plugin_timeout) self.add_cmd_output( f'oc adm inspect --kubeconfig {_kubeconfig} --dest-dir ' f'{_subdir} {_namespaces_to_collect}', suggest_filename='inspect_namespaces.log', timeout=Microshift.plugin_timeout) sos-4.8.0/sos/report/plugins/radius.py0000664000175000017500000000253614660147624016436 0ustar arifarif# Copyright (C) 2007 Navid Sheikhol-Eslami # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Radius(Plugin): short_desc = 'RADIUS service information' plugin_name = "radius" profiles = ('network', 'identity') packages = ('freeradius',) class RedHatRadius(Radius, RedHatPlugin): files = ('/etc/raddb',) def setup(self): super().setup() self.add_copy_spec([ "/etc/raddb", "/etc/pam.d/radiusd", "/var/log/radius" ]) def postproc(self): self.do_file_sub( "/etc/raddb/sql.conf", r"(\s*password\s*=\s*)\S+", r"\1***") class DebianRadius(Radius, DebianPlugin, UbuntuPlugin): files = ('/etc/freeradius',) def setup(self): super().setup() self.add_copy_spec([ "/etc/freeradius", "/etc/pam.d/radiusd", "/etc/default/freeradius", "/var/log/freeradius" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/multipath.py0000664000175000017500000000176314660147624017157 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Multipath(Plugin, IndependentPlugin): short_desc = 'Device-mapper multipath tools' plugin_name = 'multipath' profiles = ('system', 'storage', 'hardware') def setup(self): self.add_cmd_tags({ 'multipath -v4 -ll': 'multipath__v4__ll' }) self.add_copy_spec("/etc/multipath.conf", tags='multipath_conf') self.add_copy_spec("/etc/multipath/") self.add_cmd_output([ "multipath -ll", "multipath -v4 -ll", "multipath -t", "multipathd show config" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ata.py0000664000175000017500000000177014660147624015713 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Ata(Plugin, IndependentPlugin): short_desc = 'ATA and IDE information' plugin_name = "ata" profiles = ('storage', 'hardware') packages = ('hdparm', 'smartmontools') def setup(self): self.add_copy_spec('/proc/ide') cmd_list = [ "hdparm %(dev)s", "smartctl -a %(dev)s", "smartctl -a %(dev)s -j", "smartctl -l scterc %(dev)s", "smartctl -l scterc %(dev)s -j" ] self.add_device_cmd(cmd_list, devices='block', whitelist=['sd.*', 'hd.*']) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/insights.py0000664000175000017500000000477114660147624017002 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class RedHatInsights(Plugin, RedHatPlugin): """Plugin to capture configuration and logging for the Red Hat Insights client. Insights is used to provide ongoing analysis of systems for proactive problem mitigation, with the client being one of the primary sources of data for the service. This plugin will capture configuration information under /etc/insighits-client, as well as logs from /var/log/insights-client. A single connection test via the `insights-client` command is performed and recorded as well for troubleshooting purposes. """ short_desc = 'Red Hat Insights configuration and client' plugin_name = 'insights' packages = ('redhat-access-insights', 'insights-client') profiles = ('system', 'sysmgmt') config_and_status = ( '/etc/insights-client/insights-client.conf', '/etc/insights-client/.registered', '/etc/insights-client/tags.yaml', '/etc/insights-client/malware-detection-config.yml', '/etc/redhat-access-insights/redhat-access-insights.conf', '/etc/insights-client/.lastupload', '/etc/insights-client/machine-id', ) def setup(self): self.add_copy_spec(self.config_and_status) self.add_copy_spec('/var/lib/insights') # Legacy log file location self.add_copy_spec("/var/log/redhat-access-insights/*.log") if self.get_option("all_logs"): self.add_copy_spec("/var/log/insights-client/*.log*") else: self.add_copy_spec("/var/log/insights-client/insights-client.log") self.add_cmd_output( "insights-client --test-connection --net-debug", timeout=30 ) self.add_dir_listing(["/etc/rhsm", "/sys/kernel", "/var/lib/sss"], recursive=True) def postproc(self): for conf in self.config_and_status: self.do_file_sub( conf, r'(password[\t\ ]*=[\t\ ]*)(.+)', r'\1********' ) self.do_file_sub( conf, r'(proxy[\t\ ]*=.*)(:)(.*)(@.*)', r'\1\2********\4' ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/release.py0000664000175000017500000000231714660147624016564 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, \ DebianPlugin, UbuntuPlugin, CosPlugin class Release(Plugin, UbuntuPlugin, CosPlugin): short_desc = 'Linux release information' plugin_name = 'release' profiles = ('system',) def setup(self): self.add_cmd_output("lsb_release -a") self.add_cmd_output("lsb_release -d", suggest_filename="lsb_release", root_symlink="lsb-release") self.add_copy_spec([ "/etc/*release", "/etc/lsb-release/*" ]) class DebianRelease(Release, DebianPlugin): def setup(self): super().setup() self.add_copy_spec('/etc/debian_version') class RedHatRelease(Release, RedHatPlugin): def setup(self): self.add_file_tags({'/etc/redhat-release': 'redhat_release'}) super().setup() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rhv_analyzer.py0000664000175000017500000000165514660147624017654 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class RhvAnalyzer(Plugin, RedHatPlugin): short_desc = 'RHV Log Collector Analyzer' packages = ('rhv-log-collector-analyzer',) plugin_name = 'rhv_analyzer' profiles = ('virt',) def setup(self): tool_name = 'rhv-log-collector-analyzer' report = f"{self.get_cmd_output_path()}/analyzer-report.html" self.add_cmd_output([ f"{tool_name} --live --html={report}", f"{tool_name} --json" ]) # vim: expandtab tabstop=4 shiftwidth=4 sos-4.8.0/sos/report/plugins/veritas.py0000664000175000017500000000252114660147624016616 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Veritas(Plugin, RedHatPlugin): short_desc = 'Veritas software' plugin_name = 'veritas' profiles = ('cluster', 'storage') # Information about VRTSexplorer obtained from # http://seer.entsupport.symantec.com/docs/243150.htm option_list = [ PluginOpt('script', default='/opt/VRTSspt/VRTSexplorer', desc='Path to VRTSexploer script') ] def check_enabled(self): return self.path_isfile(self.get_option("script")) def setup(self): """ interface with vrtsexplorer to capture veritas related data """ ret = self.exec_cmd(self.get_option("script")) if ret['status'] == 0: tarfile = "" for line in ret['output']: line = line.strip() tarfile = self.do_regex_find_all(r"ftp (.*tar.gz)", line) if len(tarfile) == 1: self.add_copy_spec(tarfile[0]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/aap_receptor.py0000664000175000017500000000354314660147624017612 0ustar arifarif# Copyright (c) 2024 Rudnei Bertol Jr # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob from sos.report.plugins import Plugin, RedHatPlugin class AAPreceptorPlugin(Plugin, RedHatPlugin): short_desc = 'AAP receptor plugin' plugin_name = 'aap_receptor' profiles = ('sysmgmt', 'ansible') packages = ('receptor', 'receptorctl') services = ('receptor',) def setup(self): self.add_copy_spec([ "/etc/receptor", "/tmp/receptor/*/status", "/var/lib/receptor", ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/receptor" ]) else: self.add_copy_spec([ "/var/log/receptor/*.log" ]) self.add_forbidden_path([ "/etc/receptor/tls", "/etc/receptor/*key.pem" ]) self.add_dir_listing([ "/etc/receptor", "/var/run/receptor", "/var/run/awx-receptor" ]) for s in glob.glob('/var/run/*receptor/*.sock'): self.add_cmd_output(f"receptorctl --socket {s} status", suggest_filename="receptorctl_status") self.add_cmd_output(f"receptorctl --socket {s} status --json", suggest_filename="receptorctl_status.json") self.add_cmd_output(f"receptorctl --socket {s} work list", suggest_filename="receptorctl_work_list.json") break # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ptp.py0000664000175000017500000000146114660147624015746 0ustar arifarif# Copyright (C) 2015 Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Ptp(Plugin, IndependentPlugin): short_desc = 'Precision time protocol' plugin_name = "ptp" profiles = ('system', 'services') packages = ('linuxptp',) def setup(self): self.add_copy_spec([ "/etc/ptp4l.conf", "/etc/timemaster.conf", "/sys/class/ptp" ]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/rhc.py0000664000175000017500000000402114660147624015712 0ustar arifarif# Copyright (C) 2023 Red Hat, Inc., Jose Castillo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Rhc(Plugin, RedHatPlugin): """ RHC is a client tool and daemon that connects the system to Red Hat hosted services enabling system and subscription management. This plugin captures configuration files and the output of 'rhc status'. """ short_desc = 'Red Hat client for remote host configured services' plugin_name = "rhc" packages = ("rhc", ) def setup(self): self.add_copy_spec([ "/etc/rhc/*", "/var/log/rhc-worker-playbook", ]) self.add_cmd_output([ "rhc status", ]) def postproc(self): # hide workers/foreman_rh_cloud.toml FORWARDER_PASSWORD # Example for scrubbing FORWARDER_PASSWORD # # "FORWARDER_PASSWORD=F0rW4rd3RPassW0rD" # # to # # "FORWARDER_PASSWORD= ******** self.do_path_regex_sub("/etc/rhc/workers/foreman_rh_cloud.toml", r"(FORWARDER_PASSWORD\s*=\s*)(.+)(\"\,)", r"\1********\3") # hide ssh host keys from rhc-worker ansible playbooks # Example for scrubbing one of the ssh keys # # "ansible_ssh_host_key_ecdsa_public": "ABCDEFGHIJ", # # to # # "ansible_ssh_host_key_ecdsa_public": ********, path = "/var/log/rhc-worker-playbook/ansible/*" regexp = r"(\s*\"ansible_ssh_host_key_)(.+)(_public\":\s*)(.+)(\,|$)" self.do_path_regex_sub(path, regexp, r"\1\2\3********\5") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/microcloud.py0000664000175000017500000000242714660147624017306 0ustar arifarif# Copyright (C) 2024 Alan Baghumian # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class MicroCloud(Plugin, UbuntuPlugin): """The MicroCloud plugin collects the current status of the microcloud snap. It will collect journald logs as well as output from various microcloud commands. """ short_desc = 'MicroCloud Snap' plugin_name = "microcloud" profiles = ('container',) packages = ('microcloud',) def setup(self): self.add_journal(units="snap.microcloud.*") microcloud_subcmds = [ 'cluster list', '--version' ] self.add_copy_spec([ '/var/snap/microcloud/common/state/database/cluster.yaml', '/var/snap/microcloud/common/state/database/info.yaml', ]) self.add_cmd_output([ f"microcloud {subcmd}" for subcmd in microcloud_subcmds ]) # vim: set et ts=4 sw=4 sos-4.8.0/sos/report/plugins/kata_containers.py0000664000175000017500000000253214660147624020310 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class KataContainers(Plugin, IndependentPlugin): short_desc = 'Kata Containers configuration' plugin_name = 'kata_containers' profiles = ('system', 'virt', 'container') packages = ('kata-containers',) def setup(self): self.add_cmd_output('kata-runtime kata-env') config_files = set() # start with the default file locations config_files.add('/etc/kata-containers/configuration.toml') config_files.add( '/usr/share/defaults/kata-containers/configuration.toml') # obtain a list of config files by asking the runtime cmd = 'kata-runtime --kata-show-default-config-paths' configs = self.collect_cmd_output(cmd) if configs and configs['status']: for config in configs['output'].splitlines(): if config != "": config_files.add(config) self.add_copy_spec(config_files) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/charmed_postgresql.py0000664000175000017500000001406714660147624021037 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import yaml from sos.report.plugins import Plugin, UbuntuPlugin SNAP_COMMON_PATH = "/var/snap/charmed-postgresql/common" SNAP_CURRENT_PATH = "/var/snap/charmed-postgresql/current" PATHS = { "POSTGRESQL_CONF": SNAP_COMMON_PATH + "/var/lib/postgresql", "POSTGRESQL_LOGS": SNAP_COMMON_PATH + "/var/log/postgresql", "PATRONI_CONF": SNAP_CURRENT_PATH + "/etc/patroni", "PATRONI_LOGS": SNAP_COMMON_PATH + "/var/log/patroni", "PGBACKREST_CONF": SNAP_CURRENT_PATH + "/etc/pgbackrest", "PGBACKREST_LOGS": SNAP_COMMON_PATH + "/var/log/pgbackrest", "PGBOUNCER_CONF": SNAP_CURRENT_PATH + "/etc/pgbouncer", "PGBOUNCER_LOGS": SNAP_COMMON_PATH + "/var/log/pgbouncer", } PATRONI_CONFIG_FILE = f"{PATHS['PATRONI_CONF']}/patroni.yaml" RUNAS = "snap_daemon" PSQL = "charmed-postgresql.psql" PATRONICTL = "charmed-postgresql.patronictl" class CharmedPostgreSQL(Plugin, UbuntuPlugin): short_desc = "Charmed PostgreSQL" plugin_name = "charmed_postgresql" packages = ('charmed-postgresql',) @property def patronictl_args(self) -> str: return f"--config-file {PATRONI_CONFIG_FILE}" @property def psql_args(self) -> str: return (f"-U {self.postgresql_username} " f"-h {self.postgresql_host} " f"-p {self.postgresql_port} " r"-d postgres -P pager=off") def setup(self): # --- FILE EXCLUSIONS --- # Keys and certificates self.add_forbidden_path([ f"{PATHS['PATRONI_CONF']}/*.pem", f"{PATHS['PGBOUNCER_CONF']}/*.pem", ]) # --- FILE INCLUSIONS --- self.add_copy_spec([ f"{PATHS['POSTGRESQL_CONF']}/*.conf*", f"{PATHS['POSTGRESQL_LOGS']}", f"{PATHS['PATRONI_CONF']}/*.y*ml", f"{PATHS['PATRONI_LOGS']}", f"{PATHS['PGBACKREST_CONF']}", f"{PATHS['PGBACKREST_LOGS']}", f"{PATHS['PGBOUNCER_CONF']}", f"{PATHS['PGBOUNCER_LOGS']}", ]) # --- SNAP LOGS --- self.add_journal("snap.charmed-postgresql.*") # --- SNAP INFO --- self.add_cmd_output( "snap info charmed-postgresql", suggest_filename="snap-info", ) # Read and parse patroni config, finish setup if there are errors try: with open(PATRONI_CONFIG_FILE, encoding='utf-8') as f: patroni_config = yaml.safe_load(f) self.patroni_cluster_name = patroni_config["scope"] postgresql = patroni_config["postgresql"] host_port = postgresql["connect_address"].split(":") self.postgresql_host, self.postgresql_port = host_port authentication = postgresql["authentication"] superuser = authentication["superuser"] self.postgresql_username = superuser["username"] self.postgresql_password = superuser["password"] except (OSError, yaml.YAMLError, TypeError, ValueError, KeyError, AttributeError) as error: self._log_warn("Can not run additional commands due to " "an error on opening or parsing the config file " f"{PATRONI_CONFIG_FILE}: " f'{error}') return # --- TOPOLOGY --- self.add_cmd_output( (f"{PATRONICTL} {self.patronictl_args} " f"topology {self.patroni_cluster_name}"), suggest_filename="patroni-topology", runas=RUNAS, ) # --- HISTORY --- self.add_cmd_output( (f"{PATRONICTL} {self.patronictl_args} " f"history {self.patroni_cluster_name}"), suggest_filename="patroni-history", runas=RUNAS, ) # --- DCS CONFIGS --- self.add_cmd_output( (f"{PATRONICTL} {self.patronictl_args} " f"show-config {self.patroni_cluster_name}"), suggest_filename="patroni-dcs-config", runas=RUNAS, ) # ADD DB PASSWORD TO ENVIRONMENT os.environ["PGPASSWORD"] = self.postgresql_password # --- DATABASES --- self.add_cmd_output( (f"{PSQL} {self.psql_args} " r"-c '\l+'"), suggest_filename="postgresql-databases", runas=RUNAS, ) # --- USERS --- self.add_cmd_output( (f"{PSQL} {self.psql_args} " r"-c '\duS+'"), suggest_filename="postgresql-users", runas=RUNAS, ) # --- TABLES --- self.add_cmd_output( (f"{PSQL} {self.psql_args} " r"-c '\dtS+'"), suggest_filename="postgresql-tables", runas=RUNAS, ) def postproc(self): # REMOVE DB PASSWORD FROM ENVIRONMENT if "PGPASSWORD" in os.environ: del os.environ["PGPASSWORD"] # --- SCRUB PASSWORDS --- # Match lines containing password: and # followed by anything which may be enclosed with " self.do_path_regex_sub( f"{PATHS['PATRONI_CONF']}/*", r'(password: )"?.*"?', r'\1"*********"', ) # https://pgbackrest.org/configuration.html#section-repository/option-repo-s3-key # https://pgbackrest.org/configuration.html#section-repository/option-repo-s3-key-secret self.do_path_regex_sub( f"{PATHS['PGBACKREST_CONF']}/pgbackrest.conf", r'(.*s3-key.*=).*', r'\1*********', ) # https://www.pgbouncer.org/config.html#authentication-file-format self.do_path_regex_sub( f"{PATHS['PGBOUNCER_CONF']}/pgbouncer/userlist.txt", r'(".*" )".*"', r'\1"*********"', ) sos-4.8.0/sos/report/plugins/perl.py0000664000175000017500000000122614660147624016104 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Perl(Plugin, IndependentPlugin): short_desc = 'Perl runtime' plugin_name = "perl" profiles = ('webserver', 'perl') verify_packages = ('perl.*',) def setup(self): self.add_cmd_output("perl -V") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/psacct.py0000664000175000017500000000245314660147624016422 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Psacct(Plugin): short_desc = 'Process accounting information' plugin_name = "psacct" profiles = ('system',) option_list = [ PluginOpt('all', default=False, desc='collect all accounting files') ] packages = ("psacct", ) class RedHatPsacct(Psacct, RedHatPlugin): packages = ("psacct", ) def setup(self): super().setup() self.add_copy_spec("/var/account/pacct") if self.get_option("all"): self.add_copy_spec("/var/account/pacct*.gz") class DebianPsacct(Psacct, DebianPlugin, UbuntuPlugin): packages = ("acct", ) def setup(self): super().setup() self.add_copy_spec(["/var/log/account/pacct", "/etc/default/acct"]) if self.get_option("all"): self.add_copy_spec("/var/log/account/pacct*.gz") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/oddjob.py0000664000175000017500000000154714660147624016411 0ustar arifarif# Copyright (C) 2007 Sadique Puthen # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Oddjob(Plugin, RedHatPlugin): short_desc = 'OddJob task scheduler' plugin_name = 'oddjob' profiles = ('services', 'sysmgmt') files = ('/etc/oddjobd.conf',) packages = ('oddjob',) def setup(self): self.add_copy_spec([ "/etc/oddjobd.conf", "/etc/oddjobd.conf.d", "/etc/dbus-1/system.d/oddjob.conf" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/leapp.py0000664000175000017500000000205014660147624016237 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Leapp(Plugin, RedHatPlugin): short_desc = 'Leapp upgrade handling tool' plugin_name = 'leapp' packages = ('leapp', 'leapp-repository') def setup(self): self.add_copy_spec([ '/var/log/leapp/dnf-debugdata/', '/var/log/leapp/leapp-preupgrade.log', '/var/log/leapp/leapp-upgrade.log', '/var/log/leapp/leapp-report.txt', '/var/log/leapp/dnf-plugin-data.txt' ]) # capture DB without sizelimit self.add_copy_spec('/var/lib/leapp/leapp.db', sizelimit=0) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/perccli.py0000664000175000017500000000350114660147624016561 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class PercCLI(Plugin, IndependentPlugin): short_desc = 'PowerEdge RAID Controller management' plugin_name = 'perccli' profiles = ('system', 'storage', 'hardware',) packages = ('perccli',) option_list = [ PluginOpt('json', default=False, desc='collect data in JSON format') ] def setup(self): cmd = '/opt/MegaRAID/perccli/perccli64' subcmds = [ 'show ctrlcount', '/call show AliLog', '/call show all', '/call show termlog', '/call/bbu show all', '/call/cv show all', '/call/dall show', '/call/eall show all', '/call/eall/sall show all', '/call/sall show all', '/call/vall show all', ] json = ' J' if self.get_option('json') else '' logpath = self.get_cmd_output_path() for subcmd in subcmds: self.add_cmd_output( f"{cmd} {subcmd}{json}", suggest_filename=f"perccli64_{subcmd}{json}", runat=logpath) # /call show events need 'file=' option to get adapter info like below # "Adapter: # - Number of Events: xxx". subcmd = '/call show events' self.add_cmd_output( f"{cmd} {subcmd} file=/dev/stdout{json}", suggest_filename=f"perccli64_{subcmd}{json}", runat=logpath) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/discovery.py0000664000175000017500000000223414660147624017151 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Jose Castillo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Discovery(Plugin, RedHatPlugin): short_desc = 'Discovery inspection and reporting tool' plugin_name = 'discovery' packages = ('discovery', 'discovery-tools',) containers = ('dsc-db', 'discovery', 'discovery-toolbox') def setup(self): self.add_copy_spec([ "/root/discovery/db/volume/data/userdata/pg_log/", "/root/discovery/server/volumes/log/app.log", "/root/discovery/server/volumes/log/discovery-server.log", "/var/lib/containers/storage/volumes/dsc-data/_data/userdata/log/", "/var/discovery/server/volumes/log/", ]) self.add_container_logs(list(self.containers)) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/boom.py0000664000175000017500000000206314660147624016076 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Boom(Plugin, RedHatPlugin): short_desc = 'Configuration data for the boom boot manager.' plugin_name = 'boom' profiles = ('boot', 'system') packages = ( 'lvm2-python-boom', 'python-boom', 'python2-boom', 'python3-boom' ) def setup(self): self.add_copy_spec([ "/boot/boom", "/boot/loader/entries", "/etc/default/boom", "/etc/grub.d/42_boom" ]) self.add_cmd_output([ "boom list -VV", "boom profile list -VV" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/vsftpd.py0000664000175000017500000000132414660147624016447 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Vsftpd(Plugin, RedHatPlugin): short_desc = 'Vsftpd server' plugin_name = 'vsftpd' profiles = ('services',) files = ('/etc/vsftpd',) packages = ('vsftpd',) def setup(self): self.add_copy_spec([ "/etc/ftp*", "/etc/vsftpd" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kimchi.py0000664000175000017500000000176114660147624016412 0ustar arifarif# Copyright IBM, Corp. 2014, Christy Perez # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Kimchi(Plugin, IndependentPlugin): short_desc = 'kimchi-related information' plugin_name = 'kimchi' packages = ('kimchi',) def setup(self): self.add_copy_spec('/etc/kimchi/') if not self.get_option('all_logs'): self.add_copy_spec('/var/log/kimchi/*.log') self.add_copy_spec('/etc/kimchi/kimchi*') self.add_copy_spec('/etc/kimchi/distros.d/*.json') else: self.add_copy_spec('/var/log/kimchi/') # vim: expandtab tabstop=4 shiftwidth=4 sos-4.8.0/sos/report/plugins/container_log.py0000664000175000017500000000305214660147624017764 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Cedric Jeanneret # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class ContainerLog(Plugin, IndependentPlugin): short_desc = 'All logs under /var/log/containers' plugin_name = 'container_log' logdir = '/var/log/containers/' poddir = '/var/log/pods/' rotated_dirs = [poddir + '*/*.log.*', poddir + '*/*/*.log.*'] files = (logdir, ) option_list = [ PluginOpt('rotated', default=False, val_type=bool, desc='also get rotated logs from /var/log/pods'), ] def setup(self): if self.get_option('all_logs'): self.add_copy_spec(self.logdir) if self.get_option('rotated'): self.add_copy_spec(self.rotated_dirs) else: self.collect_subdirs(self.logdir, '*.log') if self.get_option('rotated'): self.collect_subdirs(self.poddir, '*.log.*') def collect_subdirs(self, root, glob): """Collect *.log files from subdirs of passed root path """ for dir_name, _, _ in os.walk(root): self.add_copy_spec(self.path_join(dir_name, glob)) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/xdp.py0000664000175000017500000000122314660147624015732 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Xdp(Plugin, IndependentPlugin): short_desc = 'XDP program information' plugin_name = 'xdp' profiles = ('system', 'kernel', 'network') packages = ('xdp-tools',) def setup(self): self.add_cmd_output('xdp-loader status') sos-4.8.0/sos/report/plugins/teamd.py0000664000175000017500000000237214660147624016237 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Teamd(Plugin, IndependentPlugin): short_desc = 'Network Interface Teaming' plugin_name = 'teamd' profiles = ('network', 'hardware', ) packages = ('teamd',) def setup(self): self.add_copy_spec([ "/etc/dbus-1/system.d/teamd.conf", "/usr/lib/systemd/system/teamd@.service" ]) self.add_device_cmd("teamdctl %(dev)s config dump", devices="team", tags="teamdctl_config_dump") self.add_device_cmd("teamdctl %(dev)s state dump", devices="team", tags="teamdctl_state_dump") self.add_device_cmd([ "teamdctl %(dev)s state", "teamnl %(dev)s option", "teamnl %(dev)s ports" ], devices='team') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_gnocchi.py0000664000175000017500000000661214660147624020627 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Sachin Patil # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Gnocchi(Plugin): short_desc = 'Gnocchi - Metric as a service' plugin_name = "openstack_gnocchi" profiles = ('openstack', 'openstack_controller') apachepkg = None def setup(self): self.add_copy_spec([ "/etc/gnocchi/*", ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/gnocchi/*", f"/var/log/{self.apachepkg}*/gnocchi*" ]) else: self.add_copy_spec([ "/var/log/gnocchi/*.log", f"/var/log/{self.apachepkg}*/gnocchi*.log" ]) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. Source " "the environment file for the user intended " "to connect to the OpenStack environment.") else: self.add_cmd_output([ "gnocchi --version", "gnocchi status", "gnocchi capabilities list", "gnocchi archive-policy list", "gnocchi resource list", "gnocchi resource-type list" ]) def postproc(self): self.do_file_sub( "/etc/gnocchi/gnocchi.conf", r"(ceph_secret|password|memcache_secret_key)\s?=(.*)", r"\1=*****", ) class RedHatGnocchi(Gnocchi, RedHatPlugin): apachepkg = 'httpd' var_puppet_gen = "/var/lib/config-data/puppet-generated/gnocchi" packages = ( 'openstack-gnocchi-metricd', 'openstack-gnocchi-common', 'openstack-gnocchi-statsd', 'openstack-gnocchi-api', 'openstack-gnocchi-carbonara' ) def setup(self): super().setup() self.add_copy_spec([ self.var_puppet_gen + "/etc/gnocchi/*", self.var_puppet_gen + "/etc/httpd/conf/*", self.var_puppet_gen + "/etc/httpd/conf.d/*", self.var_puppet_gen + "/etc/httpd/conf.modules.d/wsgi.conf", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf" ]) def postproc(self): super().postproc() self.do_file_sub( self.var_puppet_gen + "/etc/gnocchi/" "gnocchi.conf", r"(ceph_secret|password|memcache_secret_key)\s?=(.*)", r"\1=*****", ) class DebianGnocchi(Gnocchi, DebianPlugin, UbuntuPlugin): apachepkg = 'apache2' packages = ( 'gnocchi-api', 'gnocchi-metricd', 'gnocchi-common', 'gnocchi-statsd', 'python-gnocchi', 'python3-gnocchi', ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_heat.py0000664000175000017500000001251014660147624020130 0ustar arifarif# Copyright (C) 2013 Red Hat, Inc. # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackHeat(Plugin): short_desc = 'OpenStack Heat' plugin_name = "openstack_heat" profiles = ('openstack', 'openstack_controller') containers = ('.*heat_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/heat" service_name = "openstack-heat-api.service" def setup(self): # collect commands output only if the openstack-heat-api service # is running in_container = self.container_exists('.*heat_api') if self.is_service_running(self.service_name) or in_container: heat_config = "" # if containerized we need to pass the config to the cont. if in_container: heat_config = "--config-dir " + self.var_puppet_gen + \ "_api/etc/heat/" self.add_cmd_output( "heat-manage " + heat_config + " db_version", suggest_filename="heat_db_version" ) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. " "Source the environment file for the user " "intended to connect to the OpenStack " "environment.") else: self.add_cmd_output("openstack stack list --all-projects " "--nested") res = self.collect_cmd_output( "openstack stack list --all-projects" ) if res['status'] == 0: heat_stacks = res['output'] for stack in heat_stacks.splitlines()[3:-1]: stack = stack.split()[1] cmd = f"openstack stack show {stack}" self.add_cmd_output(cmd) cmd = f"openstack stack resource list {stack} -n 10" self.add_cmd_output(cmd) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/heat/", ]) else: self.add_copy_spec([ "/var/log/heat/*.log", ]) self.add_copy_spec([ "/etc/heat/", self.var_puppet_gen + "/etc/heat/", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf", self.var_puppet_gen + "_api/etc/heat/", self.var_puppet_gen + "_api/etc/httpd/conf/", self.var_puppet_gen + "_api/etc/httpd/conf.d/", self.var_puppet_gen + "_api/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "_api/var/spool/cron/heat", self.var_puppet_gen + "_api_cfn/etc/heat/", self.var_puppet_gen + "_api_cfn/etc/httpd/conf/", self.var_puppet_gen + "_api_cfn/etc/httpd/conf.d/", self.var_puppet_gen + "_api_cfn/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "_api_cfn/var/spool/cron/heat", ]) self.add_file_tags({ "/var/log/heat/heat-engine.log": "heat_engine_log" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub( "/etc/heat/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/heat/*", regexp, subst ) self.do_path_regex_sub( self.var_puppet_gen + "_api/etc/heat/*", regexp, subst ) self.do_path_regex_sub( self.var_puppet_gen + "_api_cfn/etc/heat/*", regexp, subst ) def postproc(self): protect_keys = [ "admin_password", "memcache_secret_key", "password", "qpid_password", "rabbit_password", "stack_domain_admin_password", "transport_url" ] connection_keys = ["connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianHeat(OpenStackHeat, DebianPlugin, UbuntuPlugin): packages = ( 'heat-api', 'heat-api-cfn', 'heat-api-cloudwatch', 'heat-common', 'heat-engine', 'python-heat', 'python3-heat', ) service_name = 'heat-api.service' class RedHatHeat(OpenStackHeat, RedHatPlugin): packages = ('openstack-selinux',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/powerpc.py0000664000175000017500000001034514660147624016623 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # This plugin enables collection of logs for Power systems and more # specific logs for Pseries, PowerNV platforms. from sos.report.plugins import Plugin, IndependentPlugin class PowerPC(Plugin, IndependentPlugin): short_desc = 'IBM Power systems' plugin_name = 'powerpc' profiles = ('system', 'hardware') architectures = ('ppc.*',) def setup(self): try: with open(self.path_join('/proc/cpuinfo'), 'r', encoding='UTF-8') as file: contents = file.read() isp_series = "pSeries" in contents is_power_nv = "PowerNV" in contents except IOError: isp_series = False is_power_nv = False if isp_series or is_power_nv: self.add_copy_spec([ "/proc/device-tree/", "/proc/loadavg", "/proc/locks", "/proc/misc", "/proc/swaps", "/proc/version", "/dev/nvram", "/var/lib/lsvpd/", "/var/log/lp_diag.log", "/etc/ct_node_id" ]) self.add_cmd_output([ "ppc64_cpu --info", "ppc64_cpu --smt", "ppc64_cpu --cores-present", "ppc64_cpu --cores-on", "ppc64_cpu --run-mode", "ppc64_cpu --frequency", "ppc64_cpu --dscr", "diag_encl -v", "lsvpd -D", "lsmcode -A", "lscfg -v", "opal-elog-parse -s", "opal-elog-parse -a", "opal-elog-parse -l", "lssrc -a", "lsrsrc IBM.MCP", "rmcdomainstatus -s ctrmc", "rmcdomainstatus -s ctrmc -a ip" ]) if isp_series: self.add_copy_spec([ "/proc/ppc64/lparcfg", "/proc/ppc64/eeh", "/proc/ppc64/systemcfg", "/var/log/platform", "/var/log/drmgr", "/var/log/drmgr.0", "/var/log/hcnmgr", "/var/log/rtas_errd.log", "/var/ct/IBM.DRM.stderr", "/var/ct/IW/log/mc/IBM.DRM/trace*" ]) ctsnap_path = self.get_cmd_output_path(name="ctsnap", make=True) self.add_cmd_output([ "servicelog --dump", "servicelog_notify --list", "usysattn", "usysident", "serv_config -l", "bootlist -m both -r", "lparstat -i", "lparnumascore", "lparnumascore -c cpu -d 4", "lparnumascore -c mem -d 3", f"ctsnap -xrunrpttr -d {ctsnap_path}", "lsdevinfo", "lsslot", "amsstat" ]) # Due to the lack of options in invscout for generating log files # in locations other than /var/adm/invscout/, it is necessary to # run invscout commands prior to collecting the log files. self.collect_cmd_output("invscout") self.collect_cmd_output("invscout -v") self.add_copy_spec(["/var/adm/invscout/*"]) self.add_service_status([ "hcn-init", "ctrmc" ]) if is_power_nv: self.add_copy_spec([ "/proc/ppc64/eeh", "/proc/ppc64/systemcfg", "/proc/ppc64/topology_updates", "/sys/firmware/opal/msglog", "/var/log/opal-elog/", "/var/log/opal-prd", "/var/log/opal-prd.log*" ]) self.add_cmd_output([ "opal-prd --expert-mode run nvdimm_info" ]) self.add_dir_listing('/var/log/dump') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/composer.py0000664000175000017500000000415414660147624016774 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Composer(Plugin, IndependentPlugin): short_desc = 'OSBuild Composer' plugin_name = 'composer' profiles = ('sysmgmt', 'virt', ) packages = ( 'composer-cli', 'weldr-client', 'cockpit-composer', 'osbuild-composer', ) def _get_entries(self, cmd): entries = [] ent_file = self.collect_cmd_output(cmd) if ent_file['status'] == 0: for line in ent_file['output'].splitlines(): entries.append(line) return entries def setup(self): self.add_copy_spec([ "/etc/osbuild-composer/osbuild-composer.toml", "/etc/osbuild-worker/osbuild-worker.toml", "/etc/lorax/composer.conf", "/etc/osbuild-composer", "/var/log/lorax-composer/composer.log", "/var/log/lorax-composer/dnf.log", "/var/log/lorax-composer/program.log", "/var/log/lorax-composer/server.log" ]) blueprints = self._get_entries("composer-cli blueprints list") for blueprint in blueprints: self.add_cmd_output(f"composer-cli blueprints show {blueprint}") sources = self._get_entries("composer-cli sources list") for src in sources: self.add_cmd_output(f"composer-cli sources info {src}") composes = self._get_entries("composer-cli compose list") for compose in composes: # the first column contains the compose id self.add_cmd_output( f"composer-cli compose log {compose.split(' ')[0]}" ) self.add_journal(units=[ "osbuild-composer.service", "osbuild-worker@*.service", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cockpit.py0000664000175000017500000000171014660147624016574 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Cockpit(Plugin, IndependentPlugin): short_desc = 'Cockpit Web Service' plugin_name = 'cockpit' packages = ('cockpit-ws', 'cockpit-system', 'cockpit-bridge') services = ('cockpit',) def setup(self): self.add_forbidden_path('/etc/cockpit/ws-certs.d/') self.add_copy_spec([ '/etc/cockpit/', '/etc/pam.d/cockpit' ]) self.add_cmd_output('cockpit-bridge --packages') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ipa.py0000664000175000017500000001651314660147624015720 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Kent Lamb # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, RedHatPlugin, SoSPredicate class Ipa(Plugin, RedHatPlugin): short_desc = 'Identity, policy, audit' plugin_name = 'ipa' profiles = ('identity', 'apache') ipa_server = False ipa_client = False files = ('/etc/ipa',) packages = ('ipa-server', 'ipa-client', 'freeipa-server', 'freeipa-client') pki_tomcat_dir_v4 = None pki_tomcat_dir_v3 = None pki_tomcat_conf_dir_v4 = None pki_tomcat_conf_dir_v3 = None def check_ipa_server_version(self): """ Get IPA server version """ if self.is_installed("pki-server") \ or self.path_exists("/var/lib/pki") \ or self.path_exists("/usr/share/doc/ipa-server-4.2.0"): return "v4" if self.is_installed("pki-common") \ or self.path_exists("/var/lib/pki-ca/"): return "v3" return None def ca_installed(self): """ Check if any CA is installed """ # Follow the same checks as IPA CA installer code return any( self.path_exists(path) for path in [ f"{self.pki_tomcat_dir_v4}/conf/ca/CS.cfg", f"{self.pki_tomcat_dir_v3}/conf/CS.cfg" ] ) def ipa_server_installed(self): """ Check if IPA server is installed """ return any( self.is_installed(pkg) for pkg in ['ipa-server', 'freeipa-server'] ) def collect_pki_logs(self, ipa_version): """ Collect PKI logs """ if ipa_version == "v4": self.add_copy_spec([ "/var/log/pki/pki-tomcat/ca/debug*", "/var/log/pki/pki-tomcat/ca/system", "/var/log/pki/pki-tomcat/ca/transactions", "/var/log/pki/pki-tomcat/ca/selftests.log", "/var/log/pki/pki-tomcat/catalina.*", "/var/log/pki/pki-ca-spawn.*", "/var/log/pki/pki-tomcat/kra/debug*", "/var/log/pki/pki-tomcat/kra/system", "/var/log/pki/pki-tomcat/kra/transactions", "/var/log/pki/pki-kra-spawn.*" ]) elif ipa_version == "v3": self.add_copy_spec([ "/var/log/pki-ca/debug", "/var/log/pki-ca/system", "/var/log/pki-ca/transactions", "/var/log/pki-ca/selftests.log", "/var/log/pki-ca/catalina.*", "/var/log/pki/pki-ca-spawn.*" ]) def setup(self): self.pki_tomcat_dir_v4 = "/var/lib/pki/pki-tomcat" self.pki_tomcat_dir_v3 = "/var/lib/pki-ca" self.pki_tomcat_conf_dir_v4 = "/etc/pki/pki-tomcat/ca" self.pki_tomcat_conf_dir_v3 = "/etc/pki-ca" # Returns "v3", "v4", or None ipa_version = self.check_ipa_server_version() if self.ipa_server_installed(): self._log_debug("IPA server install detected") self._log_debug(f"IPA version is [{ipa_version}]") self.add_copy_spec([ "/var/log/ipaserver-install.log", "/var/log/ipaserver-kra-install.log", "/var/log/ipaserver-enable-sid.log", "/var/log/ipareplica-install.log", "/var/log/ipareplica-ca-install.log", "/var/log/ipa-custodia.audit.log" ]) if self.ca_installed(): self._log_debug("CA is installed: retrieving PKI logs") self.collect_pki_logs(ipa_version) self.add_copy_spec([ "/var/log/ipaclient-install.log", "/var/log/ipaupgrade.log", "/var/log/krb5kdc.log", "/var/log/dirsrv/slapd-*/logs/access", "/var/log/dirsrv/slapd-*/logs/errors", "/etc/dirsrv/slapd-*/dse.ldif", "/etc/dirsrv/slapd-*/schema/99user.ldif", "/etc/hosts", "/etc/httpd/alias/*", "/etc/named.*", "/etc/ipa/ca.crt", "/etc/ipa/default.conf", "/etc/ipa/kdcproxy/kdcproxy.conf", "/etc/ipa/kdcproxy/ipa-kdc-proxy.conf", "/etc/ipa/kdcproxy.conf", "/root/.ipa/log/cli.log", "/var/lib/certmonger/requests/[0-9]*", "/var/lib/certmonger/cas/[0-9]*", "/var/lib/ipa/ra-agent.pem", "/var/lib/ipa/certs/httpd.crt", "/var/kerberos/krb5kdc/kdc.crt", "/var/lib/ipa/sysrestore/sysrestore.state", "/var/log/ipa/healthcheck/healthcheck.log*", "/var/log/ipaepn.log*" ]) # Make sure to use the right PKI config and NSS DB folders if ipa_version == "v4": pki_tomcat_dir = self.pki_tomcat_dir_v4 pki_tomcat_conf_dir = self.pki_tomcat_conf_dir_v4 else: pki_tomcat_dir = self.pki_tomcat_dir_v3 pki_tomcat_conf_dir = self.pki_tomcat_conf_dir_v3 self.add_cmd_output(f"certutil -L -d {pki_tomcat_dir}/alias") self.add_copy_spec(f"{pki_tomcat_conf_dir}/CS.cfg") self.add_forbidden_path([ "/etc/pki/nssdb/key*", "/etc/dirsrv/slapd-*/key*", "/etc/dirsrv/slapd-*/pin.txt", "/etc/dirsrv/slapd-*/pwdfile.txt", "/etc/httpd/alias/ipasession.key", "/etc/httpd/alias/key*", "/etc/httpd/alias/pin.txt", "/etc/httpd/alias/pwdfile.txt", "/etc/named.keytab", f"{pki_tomcat_dir}/alias/key*", f"{pki_tomcat_conf_dir}/flatfile.txt", f"{pki_tomcat_conf_dir}/password.conf", ]) self.add_cmd_output([ "certutil -L -d /etc/httpd/alias/", "pki-server cert-find --show-all", "pki-server subsystem-cert-validate ca", "klist -ket /etc/dirsrv/ds.keytab", "klist -ket /etc/httpd/conf/ipa.keytab", "klist -ket /var/lib/ipa/gssproxy/http.keytab" ]) self.add_dir_listing("/etc/dirsrv/slapd-*/schema/") getcert_pred = SoSPredicate(self, services=['certmonger']) self.add_cmd_output("getcert list", pred=getcert_pred, tags="getcert_list") for certdb_directory in glob("/etc/dirsrv/slapd-*/"): self.add_cmd_output(f"certutil -L -d {certdb_directory}") self.add_file_tags({ "/var/log/ipa/healthcheck/healthcheck.log": "freeipa_healthcheck_log" }) def postproc(self): match = r"(\s*arg \"password )[^\"]*" subst = r"\1********" self.do_file_sub("/etc/named.conf", match, subst) self.do_cmd_output_sub("getcert list", r"(pin=)'(\d+)'", r"\1'***'") request_logs = "/var/lib/certmonger/requests/[0-9]*" for request_log in glob(request_logs): self.do_file_sub(request_log, r"(key_pin=)(\d+)", r"\1***") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dpkg.py0000664000175000017500000000221414660147624016065 0ustar arifarif# Copyright (c) 2012 Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, DebianPlugin, UbuntuPlugin class Dpkg(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'Debian Package Management' plugin_name = 'dpkg' profiles = ('sysmgmt', 'packagemanager') def setup(self): self.add_cmd_output("dpkg -l", root_symlink="installed-debs") if self.get_option("verify"): self.add_cmd_output("dpkg -V") self.add_cmd_output("dpkg -C") self.add_copy_spec([ "/var/cache/debconf/config.dat", "/etc/debconf.conf" ]) if not self.get_option("all_logs"): self.add_copy_spec("/var/log/dpkg.log") else: self.add_copy_spec("/var/log/dpkg.log*") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/collectl.py0000664000175000017500000000136014660147624016742 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Collectl(Plugin, IndependentPlugin): short_desc = 'Collectl data' plugin_name = "collectl" profiles = ('storage', 'system', 'performance') packages = ('collectl', ) def setup(self): self.add_copy_spec([ '/etc/collectl.conf', '/var/log/collectl/' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/smclient.py0000664000175000017500000000362014660147624016760 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class SMcli(Plugin, IndependentPlugin): short_desc = 'SANtricity storage device' plugin_name = 'smclient' plugin_timeout = 900 profiles = ('system', 'storage', 'hardware',) packages = ('SMclient',) option_list = [ PluginOpt('debug', default=False, desc='capture support debug data') ] def setup(self): subcmds = [ "show storagearray;", "show storagearray connections;", "show storagearray healthstatus;", ] ssnames = [] # Get list of storage arrays result = self.collect_cmd_output('SMcli -d -S') if result['status'] == 0: for line in result['output'].splitlines(): if 'localhost' in line: ssnames.append(line.split()[0]) # Collect status of each storage array for ssname in ssnames: self.add_cmd_output([ f"SMcli localhost -n {ssname} -c '{subcmd}'" for subcmd in subcmds ]) if self.get_option("debug"): self.do_debug(ssnames) def do_debug(self, ssnames): """ Collect debug logs """ logpath = self.get_cmd_output_path(make=False) cmd = 'SMcli localhost -n' subcmd = 'save storageArray supportData file=' for ssname in ssnames: self.add_cmd_output( f"{cmd} {ssname} -c '{subcmd}\"support-{ssname}\";'", runat=logpath, timeout=450) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/buildah.py0000664000175000017500000000351314660147624016553 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Buildah(Plugin, RedHatPlugin): short_desc = 'Buildah container and image builder' plugin_name = 'buildah' packages = ('buildah',) profiles = ('container',) def setup(self): subcmds = [ 'containers', 'containers --all', 'images', 'images --all', 'version' ] self.add_cmd_output([f"buildah {sub}" for sub in subcmds]) def make_chowdah(aurdah): chowdah = self.exec_cmd(aurdah) chowdah['auutput'] = chowdah.pop('output') chowdah['is_wicked_pissah'] = chowdah.pop('status') == 0 return chowdah containahs = make_chowdah('buildah containers -n') if containahs['is_wicked_pissah']: for containah in containahs['auutput'].splitlines(): # obligatory Tom Brady goat = containah.split()[-1] self.add_cmd_output(f'buildah inspect -t container {goat}', subdir='containers') pitchez = make_chowdah('buildah images -n') if pitchez['is_wicked_pissah']: for pitchah in pitchez['auutput'].splitlines(): brady = pitchah.split()[1] self.add_cmd_output(f'buildah inspect -t image {brady}', subdir='images') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ipmitool.py0000664000175000017500000000303714660147624017000 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin class IpmiTool(Plugin, RedHatPlugin, DebianPlugin): short_desc = 'IpmiTool hardware information' plugin_name = 'ipmitool' profiles = ('hardware', 'system', ) packages = ('ipmitool',) def setup(self): cmd = "ipmitool" result = self.collect_cmd_output("ipmitool -I usb mc info") if result['status'] == 0: cmd += " -I usb" for subcmd in ['channel info', 'channel getaccess', 'lan print']: for channel in [1, 3]: self.add_cmd_output(f"{cmd} {subcmd} {channel}") # raw 0x30 0x65: Get HDD drive Fault LED State # raw 0x30 0xb0: Get LED Status self.add_cmd_output([ f"{cmd} raw 0x30 0x65", f"{cmd} raw 0x30 0xb0", f"{cmd} sel info", f"{cmd} sel elist", f"{cmd} sel list -v", f"{cmd} sensor list", f"{cmd} chassis status", f"{cmd} lan print", f"{cmd} fru print", f"{cmd} mc info", f"{cmd} sdr info", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_edpm.py0000664000175000017500000000271714660147624020144 0ustar arifarif# Copyright (C) 2023 Red Hat, Inc., Roberto Alfieri # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OpenStackEDPM(Plugin, RedHatPlugin): short_desc = 'Installation information from OpenStack EDPM deployment' plugin_name = 'openstack_edpm' profiles = ('openstack', 'openstack_edpm') services = 'edpm-container-shutdown' edpm_log_paths = [] def setup(self): # Notes: recursion is max 2 for edpm-config # Those directories are present on all OpenStack nodes self.edpm_log_paths = [ '/etc/os-net-config/', '/var/lib/config-data/', '/var/lib/edpm-config/', ] self.add_copy_spec(self.edpm_log_paths) def postproc(self): # Ensures we do not leak passwords from the edpm related locations # Other locations don't have sensitive data. regexp = r'(".*(key|password|pass|secret|database_connection))' \ r'([":\s]+)(.*[^"])([",]+)' for path in self.edpm_log_paths: self.do_path_regex_sub(path, regexp, r'\1\3*********\5') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/pcp.py0000664000175000017500000001406314660147624015727 0ustar arifarif# Copyright (C) 2014 Michele Baldessari # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from socket import gethostname from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, PluginOpt class Pcp(Plugin, RedHatPlugin, DebianPlugin): short_desc = 'Performance Co-Pilot data' plugin_name = 'pcp' profiles = ('system', 'performance') packages = ('pcp',) pcp_conffile = '/etc/pcp.conf' # size-limit of PCP logger and manager data collected by default (MB) option_list = [ PluginOpt('pmmgrlogs', default=100, desc='size limit in MB of pmmgr logs'), PluginOpt('pmloggerfiles', default=12, desc='number of pmlogger files to collect') ] pcp_sysconf_dir = None pcp_var_dir = None pcp_log_dir = None pcp_hostname = '' def pcp_parse_conffile(self): """ Parse PCP configuration """ try: with open(self.pcp_conffile, "r", encoding='UTF-8') as pcpconf: lines = pcpconf.readlines() except IOError: return False env_vars = {} for line in lines: if line.startswith('#'): continue try: (key, value) = line.strip().split('=') env_vars[key] = value except (ValueError, KeyError): # not a line for a key, value pair. Ignore the line. pass try: self.pcp_sysconf_dir = env_vars['PCP_SYSCONF_DIR'] self.pcp_var_dir = env_vars['PCP_VAR_DIR'] self.pcp_log_dir = env_vars['PCP_LOG_DIR'] except Exception: # pylint: disable=broad-except # Fail if all three env variables are not found return False return True def setup(self): sizelimit = (None if self.get_option("all_logs") else self.get_option("pmmgrlogs")) countlimit = (None if self.get_option("all_logs") else self.get_option("pmloggerfiles")) if not self.pcp_parse_conffile(): self._log_warn(f"could not parse {self.pcp_conffile}") return # Add PCP_SYSCONF_DIR (/etc/pcp) and PCP_VAR_DIR (/var/lib/pcp/config) # unconditionally. Obviously if someone messes up their /etc/pcp.conf # in a ridiculous way (i.e. setting PCP_SYSCONF_DIR to '/') this will # break badly. var_conf_dir = self.path_join(self.pcp_var_dir, 'config') self.add_copy_spec([ self.pcp_sysconf_dir, self.pcp_conffile, var_conf_dir ]) # We explicitly avoid /var/lib/pcp/config/{pmchart,pmlogconf,pmieconf, # pmlogrewrite} as in 99% of the cases they are just copies from the # rpms. It does not make up for a lot of size but it contains many # files self.add_forbidden_path([ self.path_join(var_conf_dir, 'pmchart'), self.path_join(var_conf_dir, 'pmlogconf'), self.path_join(var_conf_dir, 'pmieconf'), self.path_join(var_conf_dir, 'pmlogrewrite') ]) # Take PCP_LOG_DIR/pmlogger/`hostname` + PCP_LOG_DIR/pmmgr/`hostname` # The *default* directory structure for pmlogger is the following: # Dir: PCP_LOG_DIR/pmlogger/HOST/ (we only collect the HOST data # itself) # - YYYYMMDD.HH.MM.{N,N.index,N.meta} N in [0,1,...] # - Latest # - pmlogger.{log,log.prior} # # Can be changed via configuration in PCP_SYSCONF_DIR/pmlogger/control # As a default strategy, collect up to 100MB data from each dir. # Can be overwritten either via pcp.pcplogsize option or all_logs. self.pcp_hostname = gethostname() # Make sure we only add the two dirs if hostname is set, otherwise # we would collect everything if self.pcp_hostname != '': # collect pmmgr logs up to 'pmmgrlogs' size limit path = self.path_join(self.pcp_log_dir, 'pmmgr', self.pcp_hostname, '*') self.add_copy_spec(path, sizelimit=sizelimit, tailit=False) # collect newest pmlogger logs up to 'pmloggerfiles' count files_collected = 0 path = self.path_join(self.pcp_log_dir, 'pmlogger', self.pcp_hostname, '*') pmlogger_ls = self.exec_cmd(f"ls -t1 {path}") if pmlogger_ls['status'] == 0: for line in pmlogger_ls['output'].splitlines(): self.add_copy_spec(line, sizelimit=0) files_collected = files_collected + 1 if countlimit and files_collected == countlimit: break self.add_copy_spec([ # Collect PCP_LOG_DIR/pmcd and PCP_LOG_DIR/NOTICES self.path_join(self.pcp_log_dir, 'pmcd'), self.path_join(self.pcp_log_dir, 'NOTICES*'), # Collect PCP_VAR_DIR/pmns self.path_join(self.pcp_var_dir, 'pmns'), # Also collect any other log and config files # (as suggested by fche) self.path_join(self.pcp_log_dir, '*/*.log*'), self.path_join(self.pcp_log_dir, '*/*/*.log*'), self.path_join(self.pcp_log_dir, '*/*/config*') ]) # Collect a summary for the current day res = self.collect_cmd_output('pcp') if res['status'] == 0: for line in res['output'].splitlines(): if line.startswith(' pmlogger:'): arc = line.split()[-1] self.add_cmd_output( f"pmstat -S 00:00 -T 23:59 -t 5m -x -a {arc}", root_symlink="pmstat" ) break # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/devicemapper.py0000664000175000017500000000221114660147624017601 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate class DeviceMapper(Plugin, IndependentPlugin): short_desc = 'device-mapper framework' plugin_name = 'devicemapper' profiles = ('storage',) packages = ('device-mapper',) kernel_mods = ('dm_mod', ) files = ('/dev/mapper',) def setup(self): self.add_cmd_output([ "dmsetup info -c", "dmsetup table", "dmsetup status", "dmsetup ls --tree", "dmsetup udevcookies", "dmstats list", "dmstats print --allregions" ], pred=SoSPredicate(self, kmods=['dm_mod'])) self.add_cmd_tags({ "dmsetup info -c": "dmsetup_info", "dmsetup status": "dmsetup_status" }) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/dbus.py0000664000175000017500000000155514660147624016104 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Dbus(Plugin, IndependentPlugin): short_desc = 'D-Bus message bus' plugin_name = "dbus" profiles = ('system',) packages = ('dbus',) def setup(self): self.add_copy_spec([ "/etc/dbus-1", "/var/lib/dbus/machine-id" ]) self.add_cmd_output([ "busctl list --no-pager", "busctl status" ]) self.add_env_var('DBUS_SESSION_BUS_ADDRESS') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovirt_hosted_engine.py0000664000175000017500000000424414660147624021203 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Sandro Bonazzola # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OvirtHostedEngine(Plugin, RedHatPlugin): short_desc = 'oVirt Hosted Engine' packages = ( 'ovirt-hosted-engine-setup', 'ovirt-hosted-engine-ha', ) plugin_name = 'ovirt_hosted_engine' profiles = ('virt',) def setup(self): # Add configuration files # Collecting the whole directory since it may contain branding # configuration files or third party plugins configuration files self.add_copy_spec(['/etc/ovirt-hosted-engine-setup.env.d/']) self.add_copy_spec([ '/etc/ovirt-hosted-engine/answers.conf', '/etc/ovirt-hosted-engine/hosted-engine.conf', '/etc/ovirt-hosted-engine/vm.conf', '/etc/ovirt-hosted-engine-ha/agent.conf', '/etc/ovirt-hosted-engine-ha/agent-log.conf', '/etc/ovirt-hosted-engine-ha/broker.conf', '/etc/ovirt-hosted-engine-ha/broker-log.conf', '/etc/ovirt-hosted-engine-ha/notifications/state_transition.txt', '/run/ovirt-hosted-engine-ha/vm.conf', '/var/run/ovirt-hosted-engine-ha/vm.conf', '/var/lib/ovirt-hosted-engine-ha/broker.conf', ]) self.add_copy_spec([ '/var/log/ovirt-hosted-engine-setup', '/var/log/ovirt-hosted-engine-ha/agent.log*', '/var/log/ovirt-hosted-engine-ha/broker.log*', ]) # Add gluster deployment and cleanup log self.add_copy_spec([ '/var/log/cockpit/ovirt-dashboard' ]) # Add run-time status self.add_cmd_output([ 'hosted-engine --vm-status', 'hosted-engine --check-liveliness', ]) # vim: expandtab tabstop=4 shiftwidth=4 sos-4.8.0/sos/report/plugins/canonical_livepatch_onprem.py0000664000175000017500000000234014660147624022506 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class CanonicaLivepatchOnprem(Plugin, UbuntuPlugin): short_desc = 'Canonical Livepatch Onprem Service' plugin_name = 'canonical_livepatch_onprem' profiles = ('services',) services = ("livepatch-server",) def setup(self): self.add_copy_spec([ "/etc/livepatchd.yaml", ]) def postproc(self): onprem_conf = "/etc/livepatchd.yaml" protect_keys = [ "username", "password", "token", "connection_string", ] # Redact simple yaml style "key: value". keys_regex = fr"(^(-|\s)*({'|'.join(protect_keys)})\s*:\s*)(.*)" sub_regex = r"\1*********" self.do_path_regex_sub(onprem_conf, keys_regex, sub_regex) # Redact conf self.do_file_private_sub(onprem_conf) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/qpid.py0000664000175000017500000000703314660147624016101 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Qpid(Plugin, RedHatPlugin): short_desc = 'Qpid messaging' plugin_name = 'qpid' profiles = ('services',) packages = ('qpidd', 'qpid-cpp-server', 'qpid-tools') option_list = [ PluginOpt('port', default='', val_type=int, desc='listening port to connect to'), PluginOpt('ssl-certificate', default='', val_type=str, desc='Path to file containing client SSL certificate'), PluginOpt('ssl-key', default='', val_type=str, desc='Path to file containing client SSL private key'), PluginOpt('ssl', default=False, desc='enforce SSL amqps connection') ] def setup(self): """ performs data collection for qpid broker """ options = "" amqps_prefix = "" # set amqps:// when SSL is used if self.get_option("ssl"): amqps_prefix = "amqps://" # for either present option, add --option=value to 'options' variable for option in ["ssl-certificate", "ssl-key"]: if self.get_option(option): amqps_prefix = "amqps://" options = options + f" --{option}={self.get_option(option)}" if self.get_option("port"): options = (options + " -b " + amqps_prefix + f"localhost:{self.get_option('port')}") self.add_cmd_output([ "qpid-stat -g" + options, # applies since 0.18 version "qpid-stat -b" + options, # applies to pre-0.18 versions "qpid-stat -c" + options, "qpid-stat -e" + options, "qpid-stat -q" + options, "qpid-stat -u" + options, "qpid-stat -m" + options, # applies since 0.18 version "qpid-config exchanges" + options, "qpid-config queues" + options, "qpid-config exchanges -b" + options, # applies to pre-0.18 vers. "qpid-config queues -b" + options, # applies to pre-0.18 versions "qpid-config exchanges -r" + options, # applies since 0.18 version "qpid-config queues -r" + options, # applies since 0.18 version "qpid-route link list" + options, "qpid-route route list" + options, "qpid-cluster" + options, # applies to pre-0.22 versions "qpid-ha query" + options, # applies since 0.22 version ]) self.add_dir_listing('/var/lib/qpidd', recursive=True) self.add_copy_spec([ "/etc/qpidd.conf", # applies to pre-0.22 versions "/etc/qpid/qpidd.conf", # applies since 0.22 version "/var/lib/qpid/syslog", "/var/lib/qpidd/.qpidd/qls/dat2/DB_CONFIG", "/var/lib/qpidd/qls/dat2/DB_CONFIG", "/etc/ais/openais.conf", "/var/log/cumin.log", "/var/log/mint.log", "/etc/sasl2/qpidd.conf", "/etc/qpid/qpidc.conf", "/etc/sesame/sesame.conf", "/etc/cumin/cumin.conf", "/etc/corosync/corosync.conf", "/var/lib/sesame", "/var/log/qpidd.log", "/var/log/sesame", "/var/log/cumin" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/coredump.py0000664000175000017500000000271414660147624016763 0ustar arifarif# Copyright (C) 2023 Red Hat, Inc., Jose Castillo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Coredump(Plugin, IndependentPlugin): short_desc = 'Retrieve coredump information' plugin_name = "coredump" profiles = ('system', 'debug') packages = ('systemd-udev', 'systemd-coredump') option_list = [ PluginOpt("detailed", default=False, desc="collect detailed information for every report") ] def setup(self): self.add_copy_spec([ "/etc/systemd/coredump.conf", "/etc/systemd/coredump.conf.d/", "/run/systemd/coredump.conf.d/", "/usr/lib/systemd/coredump.conf.d/" ]) self.add_cmd_output("coredumpctl dump") coredump_list = self.collect_cmd_output("coredumpctl list") if self.get_option("detailed") and coredump_list['status'] == 0: for line in coredump_list["output"].splitlines()[1:]: self.add_cmd_output("coredumpctl info " f"{line.split()[4]}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/alternatives.py0000664000175000017500000000443114660147624017644 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class Alternatives(Plugin): short_desc = 'System alternatives' plugin_name = 'alternatives' alternatives_cmd = None alternatives_list = None def setup(self): self.add_cmd_output(f'{self.alternatives_cmd} --version') alts = [] ignore = [ 'cdrecord', 'ld', 'mkisofs', 'whois', 'xinputrc' ] res = self.collect_cmd_output(self.alternatives_list) if res['status'] == 0: for line in res['output'].splitlines(): alt = line.split()[0] if alt not in ignore: alts.append(alt) disp_cmd = f"{self.alternatives_cmd} --display %s" self.add_cmd_output([disp_cmd % alt for alt in alts]) class RedHatAlternatives(Alternatives, RedHatPlugin): packages = ('alternatives',) commands = ('alternatives',) alternatives_cmd = 'alternatives' alternatives_list = f'{alternatives_cmd} --list' def setup(self): super().setup() self.add_cmd_tags({ "alternatives --display java.*": 'display_java', "alternatives --display python.*": 'alternatives_display_python' }) class UbuntuAlternatives(Alternatives, UbuntuPlugin): packages = ('dpkg',) commands = ('update-alternatives',) alternatives_cmd = 'update-alternatives' alternatives_list = f'{alternatives_cmd} --get-selections' def setup(self): super().setup() if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/alternatives.log*", ]) else: self.add_copy_spec([ "/var/log/alternatives.log", "/var/log/alternatives.log.1", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/gcp.py0000664000175000017500000001260014660147624015711 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from http.client import HTTPResponse from typing import Any from urllib import request from urllib.error import URLError from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class GCP(Plugin, IndependentPlugin): short_desc = 'Google Cloud Platform' plugin_name = 'gcp' profiles = ('virt',) option_list = [ PluginOpt('keep-pii', default=False, desc="Stop the plugin from removing PIIs like project name " "or organization ID from the metadata retrieved from " "Metadata server.") ] METADATA_ROOT = "http://metadata.google.internal/computeMetadata/v1/" METADATA_QUERY = "http://metadata.google.internal/computeMetadata/v1/" \ "?recursive=true" REDACTED = "[--REDACTED--]" metadata = None # A line we will be looking for in the dmesg output. If it's there, # that means we're running on a Google Cloud Compute instance. GOOGLE_DMI = "DMI: Google Google Compute Engine/Google " \ "Compute Engine, BIOS Google" def check_enabled(self): """ Checks if this plugin should be executed at all. In this case, it will check the `dmesg` command output to see if the system is running on a Google Cloud Compute instance. """ dmesg = self.exec_cmd("dmesg") if dmesg['status'] != 0: return False return self.GOOGLE_DMI in dmesg['output'] def setup(self): """ Collect the following info: * Metadata from the Metadata server * `gcloud auth list` output * Any google services output from journal """ # Capture gcloud auth list self.add_cmd_output("gcloud auth list", tags=['gcp']) # Add journal entries self.add_journal(units="google*", tags=['gcp']) def collect(self): # Get and store Metadata with self.collection_file('metadata.json', tags=['gcp']) as mfile: try: self.metadata = self.get_metadata() self.scrub_metadata() mfile.write(json.dumps(self.metadata, indent=4)) except RuntimeError as err: mfile.write(str(err)) def get_metadata(self) -> dict: """ Retrieves metadata from the Metadata Server and transforms it into a dictionary object. """ response = self._query_address(self.METADATA_QUERY) response_body = response.read().decode() return json.loads(response_body) @staticmethod def _query_address(url: str) -> HTTPResponse: """ Query the given url address with headers required by Google Metadata Server. """ try: req = request.Request(url, headers={'Metadata-Flavor': 'Google'}) with request.urlopen(req) as response: if response.code != 200: raise RuntimeError( f"Failed to communicate with Metadata Server " f"(code: {response.code}): " + response.read().decode()) return response except URLError as err: raise RuntimeError( "Failed to communicate with Metadata Server: " + str(err)) \ from err def scrub_metadata(self): """ Remove all PII information from metadata, unless a keep-pii option is specified. Note: PII information collected by this plugin, like project number, account names etc. might be required by Google Cloud Support for faster issue resolution. """ if self.get_option('keep-pii'): return project_id = self.metadata['project']['projectId'] project_number_int = self.metadata['project']['numericProjectId'] project_number = str(project_number_int) def scrub(data: Any) -> Any: if isinstance(data, dict): if 'token' in data: # Data returned for recursive query shouldn't contain # tokens, but you can't be too careful. data['token'] = self.REDACTED return {scrub(k): scrub(v) for k, v in data.items()} if isinstance(data, list): return [scrub(value) for value in data] if isinstance(data, str): return data.replace(project_number, self.REDACTED)\ .replace(project_id, self.REDACTED) if isinstance(data, int): return self.REDACTED if data == project_number_int else data return data self.metadata = scrub(self.metadata) self.safe_redact_key(self.metadata['project']['attributes'], 'ssh-keys') self.safe_redact_key(self.metadata['project']['attributes'], 'sshKeys') @classmethod def safe_redact_key(cls, dict_obj: dict, key: str): """ Redact keys """ if key in dict_obj: dict_obj[key] = cls.REDACTED sos-4.8.0/sos/report/plugins/openstack_masakarimonitors.py0000664000175000017500000000300214660147624022566 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class OpenStackMasakariMonitors(Plugin, UbuntuPlugin): short_desc = 'OpenStack Masakari Monitors' plugin_name = "openstack_masakarimonitors" profiles = ('openstack', 'openstack_controller') packages = ('masakari-monitors-common', ) services = ( 'masakari-host-monitor', 'masakari-instance-monitor', 'masakari-process-monitor', ) config_dir = "/etc/masakarimonitors" def setup(self): self.add_copy_spec([ self.config_dir, ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/masakarimonitors/*", ]) else: self.add_copy_spec([ "/var/log/masakarimonitors/*.log", ]) self.add_file_tags({ f"{self.config_dir}/masakarimonitors.conf": "masakarimonitors_conf" }) def postproc(self): protect_keys = [".*password.*"] self.do_path_regex_sub( f"{self.config_dir}/*", fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/openstack_ceilometer.py0000664000175000017500000000636614660147624021353 0ustar arifarif# Copyright (C) 2013 Red Hat, Inc., Eoghan Lynn # Copyright (C) 2012 Rackspace US, Inc. # 2012 Justin Shepherd # Copyright (C) 2009 Red Hat, Inc. # 2009 Joey Boggs # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackCeilometer(Plugin): short_desc = 'Openstack Ceilometer' plugin_name = "openstack_ceilometer" profiles = ('openstack', 'openstack_controller', 'openstack_compute') var_puppet_gen = "/var/lib/config-data/puppet-generated/ceilometer" def setup(self): # Ceilometer if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/ceilometer/*", ]) else: self.add_copy_spec([ "/var/log/ceilometer/*.log", ]) self.add_copy_spec([ "/etc/ceilometer/*", self.var_puppet_gen + "/etc/ceilometer/*" ]) self.add_file_tags({ "/var/log/ceilometer/central.log": "ceilometer_central_log" }) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/ceilometer/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/ceilometer/*", regexp, subst ) def postproc(self): protect_keys = [ "admin_password", "connection_password", "host_password", "memcache_secret_key", "os_password", "password", "qpid_password", "rabbit_password", "readonly_user_password", "secret_key", "ssl_key_password", "telemetry_secret", "metering_secret" ] connection_keys = ["connection", "backend_url", "transport_url"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianCeilometer(OpenStackCeilometer, DebianPlugin, UbuntuPlugin): packages = ( 'ceilometer-api', 'ceilometer-agent-central', 'ceilometer-agent-compute', 'ceilometer-agent-notification', 'ceilometer-collector', 'ceilometer-common', 'python-ceilometer', 'python3-ceilometer', ) class RedHatCeilometer(OpenStackCeilometer, RedHatPlugin): packages = ('openstack-selinux',) def setup(self): super().setup() if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/containers/ceilometer/*", ]) else: self.add_copy_spec([ "/var/log/containers/ceilometer/*.log", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/crio.py0000664000175000017500000000727114660147624016104 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. Daniel Walsh # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, UbuntuPlugin, SoSPredicate, PluginOpt, CosPlugin) class CRIO(Plugin, RedHatPlugin, UbuntuPlugin, CosPlugin): short_desc = 'CRI-O containers' plugin_name = 'crio' profiles = ('container',) packages = ('cri-o', 'cri-tools') services = ('crio',) option_list = [ PluginOpt('all', default=False, desc='collect for all containers, even terminated ones'), PluginOpt('logs', default=False, desc='collect stdout/stderr logs for containers') ] def setup(self): self.add_copy_spec([ "/etc/containers", "/etc/crictl.yaml", "/etc/crio/crio.conf", "/etc/crio/seccomp.json", "/etc/crio/crio.conf.d/", "/etc/systemd/system/cri-o.service", "/etc/sysconfig/crio-*" ]) self.add_env_var([ 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'ALL_PROXY' ]) self.add_cmd_output([ "crio config" ]) self.add_dir_listing('/etc/cni', recursive=True) # base cri-o installation does not require cri-tools, which is what # supplies the crictl utility self.set_cmd_predicate(SoSPredicate(self, packages=['cri-tools'])) subcmds = [ 'info', 'images', 'pods', 'ps', 'ps -a', 'ps -v', 'stats', 'version', ] self.add_cmd_output([f"crictl {s}" for s in subcmds]) ps_cmd = 'crictl ps --quiet' if self.get_option('all'): ps_cmd = f"{ps_cmd} -a" img_cmd = 'crictl images --quiet' pod_cmd = 'crictl pods --quiet' containers = self._get_crio_list(ps_cmd) images = self._get_crio_list(img_cmd) pods = self._get_crio_list(pod_cmd) self._get_crio_goroutine_stacks() for container in containers: self.add_cmd_output(f"crictl inspect {container}", subdir="containers") if self.get_option('logs'): self.add_cmd_output(f"crictl logs -t {container}", subdir="containers/logs", priority=100, tags="crictl_logs") for image in images: self.add_cmd_output(f"crictl inspecti {image}", subdir="images") for pod in pods: self.add_cmd_output(f"crictl inspectp {pod}", subdir="pods") def _get_crio_list(self, cmd): ret = [] result = self.exec_cmd(cmd) if result['status'] == 0: for ent in result['output'].splitlines(): ret.append(ent) # Prevent the socket deprecation warning from being iterated over if ret and 'deprecated' in ret[0]: ret.pop(0) return ret def _get_crio_goroutine_stacks(self): result = self.exec_cmd("pidof crio") if result['status'] != 0: return pid = result['output'].strip() result = self.exec_cmd("kill -USR1 " + pid) if result['status'] == 0: self.add_copy_spec("/tmp/crio-goroutine-stacks*.log") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/apache.py0000664000175000017500000001226314660147624016366 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Apache(Plugin): """The Apache plugin covers the upstream Apache webserver project, regardless of the packaged name; apache2 for Debian and Ubuntu, or httpd for Red Hat family distributions. The aim of this plugin is for Apache-specific information, not necessarily other projects that happen to place logs or similar files within the standardized apache directories. For example, OpenStack components that log to apache logging directories are excluded from this plugin and collected via their respective OpenStack plugins. Users can expect the collection of apachectl command output, apache server logs, and apache configuration files from this plugin. """ short_desc = 'Apache http daemon' plugin_name = "apache" profiles = ('webserver', 'openshift') packages = ('httpd',) files = ('/var/www/',) apachepkg = None option_list = [ PluginOpt(name="log", default=False, desc="gathers all apache logs") ] def setup(self): # collect list of installed modules and verify config syntax. self.add_cmd_output([ "apachectl -S", "apachectl -t" ], cmd_as_tag=True) self.add_cmd_output("apachectl -M", tags="httpd_M") # Other plugins collect these files; # do not collect them here to avoid collisions in the archive paths. subdirs = [ 'aodh', 'ceilometer', 'cinder', 'foreman', 'gnocchi', 'horizon', 'keystone', 'manila', 'nova', 'octavia', 'placement', 'pulp' ] self.add_forbidden_path([ f"/var/log/{self.apachepkg}*/{sub}*" for sub in subdirs ]) class RedHatApache(Apache, RedHatPlugin): """ On Red Hat distributions, the Apache plugin will also attempt to collect JBoss Web Server logs and configuration files. Note that for Red Hat distributions, this plugin explicitly collects for 'httpd' installations. If you have installed apache from source or via any method that uses the name 'apache' instead of 'httpd', these collections will fail. """ files = ( '/etc/httpd/conf/httpd.conf', '/etc/httpd22/conf/httpd.conf', '/etc/httpd24/conf/httpd.conf' ) apachepkg = 'httpd' def setup(self): self.add_file_tags({ "/var/log/httpd/access_log": 'httpd_access_log', "/var/log/httpd/error_log": 'httpd_error_log', "/var/log/httpd/ssl_access_log": 'httpd_ssl_access_log', "/var/log/httpd/ssl_error_log": 'httpd_ssl_error_log' }) super().setup() # httpd versions, including those used for JBoss Web Server vers = ['', '22', '24'] # Extrapolate all top-level config directories for each version, and # relevant config files within each etcdirs = [f"/etc/httpd{ver}" for ver in vers] confs = [ "conf/*.conf", "conf.d/*.conf", "conf.modules.d/*.conf" ] # Extrapolate top-level logging directories for each version, and the # relevant log files within each logdirs = [f"/var/log/httpd{ver}" for ver in vers] logs = [ "access_log", "error_log", "ssl_access_log", "ssl_error_log" ] self.add_forbidden_path([ f"{etc}/conf/password.conf" for etc in etcdirs ]) for edir in etcdirs: for conf in confs: self.add_copy_spec(f"{edir}/{conf}", tags="httpd_conf") if self.get_option("log") or self.get_option("all_logs"): self.add_copy_spec(logdirs) else: for ldir in logdirs: for log in logs: self.add_copy_spec(f"{ldir}/{log}") self.add_service_status('httpd', tags='systemctl_httpd') class DebianApache(Apache, DebianPlugin, UbuntuPlugin): files = ('/etc/apache2/apache2.conf',) apachepkg = 'apache2' def setup(self): super().setup() self.add_copy_spec([ "/etc/apache2/*", "/etc/default/apache2" ]) self.add_service_status('apache2') # collect only the current log set by default self.add_copy_spec([ "/var/log/apache2/access.log", "/var/log/apache2/error.log", "/var/log/apache2/ssl_access.log", "/var/log/apache2/ssl_error.log", "/var/log/apache2/other_vhosts_access.log", ]) if self.get_option("log") or self.get_option("all_logs"): self.add_copy_spec([ "/var/log/apache2", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/hpasm.py0000664000175000017500000000160214660147624016250 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Hpasm(Plugin, IndependentPlugin): short_desc = 'HP Advanced Server Management' plugin_name = 'hpasm' profiles = ('system', 'hardware') packages = ('hp-health',) def setup(self): self.add_copy_spec("/var/log/hp-health/hpasmd.log") self.add_cmd_output([ "hpasmcli -s 'show asr'", "hpasmcli -s 'show server'" ], timeout=0) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/vdsm.py0000664000175000017500000001206114660147624016112 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob import json import re from sos.report.plugins import Plugin, RedHatPlugin # This configuration is based on vdsm.storage.lvm.LVM_CONF_TEMPLATE. # # locking_type is set to 0 in order to match lvm sos commands. With this # configuration we don't take any locks, so we will never block because # there is a stuck lvm command. # locking_type=0 # # To prevent modifications to volume group metadata (for e.g. due to a # automatically detected inconsistency), metadata_read_only is set to 1. # metadata_read_only=1 # # use_lvmetad is set to 0 in order not to show cached, old lvm metadata. # use_lvmetad=0 # # preferred_names, use_devicesfile and filter config values are set to # capture Vdsm devices. # preferred_names=[ '^/dev/mapper/' ] # filter=[ 'a|^/dev/mapper/.*|', 'r|.*|' ] LVM_CONFIG = """ global { locking_type=0 metadata_read_only=1 use_lvmetad=0 } devices { preferred_names=["^/dev/mapper/"] ignore_suspended_devices=1 write_cache_state=0 disable_after_error_count=3 use_devicesfile=0 filter=["a|^/dev/disk/by-id/dm-uuid-mpath-|", "r|.+|"] } """ LVM_CONFIG = re.sub(r"\s+", " ", LVM_CONFIG).strip() class Vdsm(Plugin, RedHatPlugin): short_desc = 'VDSM - Virtual Desktop and Server Manager' packages = ( 'vdsm', 'vdsm-client', ) plugin_name = 'vdsm' def setup(self): self.add_forbidden_path('/etc/pki/vdsm/keys') self.add_forbidden_path('/etc/pki/vdsm/*/*-key.*') self.add_forbidden_path('/etc/pki/libvirt/private') self.add_forbidden_path('/var/lib/vdsm/storage/transient_disks') self.add_service_status(['vdsmd', 'supervdsmd']) self.add_copy_spec([ '/tmp/vds_installer*', '/tmp/vds_bootstrap*', '/etc/vdsm/*', '/etc/pki/vdsm/' ]) self.add_copy_spec('/var/log/vdsm/*') self._add_vdsm_forbidden_paths() self.add_copy_spec([ '/run/vdsm/*', '/usr/libexec/vdsm/hooks', '/var/lib/vdsm', ]) self.add_file_tags({ "/etc/vdsm/vdsm.conf": "vdsm_conf", "/etc/vdsm/vdsm.id": "vdsm_id", "/var/log/vdsm/import/import-*.log": "vdsm_import_log" }) qemu_pids = self.get_process_pids('qemu-kvm') if qemu_pids: files = ["cmdline", "status", "mountstats"] self.add_copy_spec([ f"/proc/{pid}/{name}" for pid in qemu_pids for name in files ]) self.add_dir_listing( ['/etc/vdsm', '/rhev/data-center'], runas='vdsm', recursive=True ) self.add_dir_listing('/rhev/data-center', tree=True) self.add_cmd_output([ f"lvm vgs -v -o +tags --config \'{LVM_CONFIG}\'", f"lvm lvs -v -o +tags --config \'{LVM_CONFIG}\'", f"lvm pvs -v -o +all --config \'{LVM_CONFIG}\'" ]) self.add_cmd_output([ 'vdsm-client Host getCapabilities', 'vdsm-client Host getStats', 'vdsm-client Host getAllVmStats', 'vdsm-client Host getVMFullList', 'vdsm-client Host getDeviceList', 'vdsm-client Host hostdevListByCaps', 'vdsm-client Host getAllTasksInfo', 'vdsm-client Host getAllTasksStatuses' ]) try: res = self.collect_cmd_output( 'vdsm-client Host getConnectedStoragePools' ) if res['status'] == 0: pools = json.loads(res['output']) for pool in pools: self.add_cmd_output( f"vdsm-client StoragePool getSpmStatus " f"storagepoolID={pool}" ) except ValueError as err: self._log_error( f'vdsm-client Host getConnectedStoragePools: {err}' ) try: res = self.collect_cmd_output('vdsm-client Host getStorageDomains') if res['status'] == 0: sd_uuids = json.loads(res['output']) dump_volume_chains_cmd = 'vdsm-tool dump-volume-chains %s' self.add_cmd_output([ dump_volume_chains_cmd % uuid for uuid in sd_uuids ]) except ValueError as err: self._log_error( f'vdsm-client Host getStorageDomains: {err}' ) def _add_vdsm_forbidden_paths(self): """Add confidential sysprep vfds under /run/vdsm to forbidden paths """ for file_path in glob.glob("/run/vdsm/*"): if file_path.endswith(('.vfd', '/isoUploader', '/storage')): self.add_forbidden_path(file_path) sos-4.8.0/sos/report/plugins/ceph_common.py0000664000175000017500000000726614660147624017443 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from socket import gethostname from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephCommon(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'CEPH common' plugin_name = 'ceph_common' profiles = ('storage', 'virt', 'container', 'ceph') containers = ('ceph-(.*-)?(mon|rgw|osd).*',) ceph_hostname = gethostname() packages = ( 'ceph', 'ceph-mds', 'ceph-common', 'libcephfs1', 'ceph-fs-common', 'calamari-server', ) services = ( 'ceph-nfs@pacemaker', f'ceph-mds@{ceph_hostname}', f'ceph-mon@{ceph_hostname}', f'ceph-mgr@{ceph_hostname}', 'ceph-radosgw@*', 'ceph-osd@*' ) # This check will enable the plugin regardless of being # containerized or not files = ('/etc/ceph/ceph.conf', '/var/snap/microceph/*',) def setup(self): all_logs = self.get_option("all_logs") microceph_pkg = self.policy.package_manager.pkg_by_name('microceph') if not microceph_pkg: self.add_file_tags({ '.*/ceph.conf': 'ceph_conf', '/var/log/ceph(.*)?/ceph.log.*': 'ceph_log', }) if not all_logs: self.add_copy_spec([ "/var/log/calamari/*.log", "/var/log/ceph/**/ceph.log", ]) else: self.add_copy_spec([ "/var/log/calamari", "/var/log/ceph/**/ceph.log*", ]) self.add_copy_spec([ "/var/log/ceph/**/ceph.audit.log*", "/etc/ceph/", "/etc/calamari/", "/var/lib/ceph/tmp/", ]) self.add_forbidden_path([ "/etc/ceph/*keyring*", "/var/lib/ceph/*keyring*", "/var/lib/ceph/*/*keyring*", "/var/lib/ceph/*/*/*keyring*", "/var/lib/ceph/osd", "/var/lib/ceph/mon", # Excludes temporary ceph-osd mount location like # /var/lib/ceph/tmp/mnt.XXXX from sos collection. "/var/lib/ceph/tmp/*mnt*", "/etc/ceph/*bindpass*" ]) else: if not all_logs: self.add_copy_spec([ "/var/snap/microceph/common/logs/ceph.log", "/var/snap/microceph/common/logs/ceph.audit.log", ]) else: self.add_copy_spec([ "/var/snap/microceph/common/logs/ceph.log*", "/var/snap/microceph/common/logs/ceph.audit.log*", ]) self.add_cmd_output("snap info microceph", subdir="microceph") cmds = [ 'client config list', 'cluster config list', 'cluster list', # exclude keyrings from the config db 'cluster sql \'select * from config where key NOT LIKE \ \"%keyring%\"\'', 'disk list', 'log get-level', 'status', ] self.add_cmd_output([f"microceph {cmd}" for cmd in cmds], subdir='microceph') self.add_cmd_output([ "ceph -v", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/networking.py0000664000175000017500000003063014660147624017332 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, UbuntuPlugin, DebianPlugin, SoSPredicate, PluginOpt) class Networking(Plugin): short_desc = 'Network and networking devices configuration' plugin_name = "networking" profiles = ('network', 'hardware', 'system') trace_host = "www.example.com" option_list = [ PluginOpt("traceroute", default=False, desc=f"collect a traceroute to {trace_host}"), PluginOpt("namespace-pattern", default="", val_type=str, desc=("Specific namespace names or patterns to collect, " "whitespace delimited.")), PluginOpt("namespaces", default=None, val_type=int, desc="Number of namespaces to collect, 0 for unlimited"), PluginOpt("ethtool-namespaces", default=True, desc=("Toggle if ethtool commands should be run for each " "namespace")), PluginOpt("eepromdump", default=False, desc="Toggle collection of 'ethtool -e' for NICs") ] # switch to enable netstat "wide" (non-truncated) output mode ns_wide = "-W" # list of kernel modules needed by ss_cmd, this may vary by distro version ss_kmods = ['tcp_diag', 'udp_diag', 'inet_diag', 'unix_diag', 'netlink_diag', 'af_packet_diag', 'xsk_diag'] # list of ethtool short options, used in add_copy_spec and add_cmd_tags # do NOT add there "e" (see eepromdump plugopt) ethtool_shortopts = "acdgiklmPST" def setup(self): super().setup() self.add_file_tags({ '/proc/net/bonding/bond.*': 'bond', '/etc/hosts': 'hosts' }) self.add_copy_spec([ "/etc/dnsmasq*", "/etc/host*", "/etc/inetd.conf", "/etc/iproute2", "/etc/network*", "/etc/nsswitch.conf", "/etc/resolv.conf", "/etc/xinetd.conf", "/etc/xinetd.d", "/etc/yp.conf", "/proc/net/", "/sys/class/net/*/device/numa_node", "/sys/class/net/*/flags", "/sys/class/net/*/statistics/", ]) self.add_forbidden_path([ "/proc/net/rpc/use-gss-proxy", "/proc/net/rpc/*/channel", "/proc/net/rpc/*/flush", # Cisco CDP "/proc/net/cdp", "/sys/net/cdp", # Dialogic Diva "/proc/net/eicon" ]) self.add_cmd_output("ip -o addr", root_symlink="ip_addr", tags='ip_addr') self.add_cmd_output("ip route show table all", root_symlink="ip_route", tags=['ip_route', 'iproute_show_table_all']) self.add_cmd_output("plotnetcfg") self.add_cmd_output(f"netstat {self.ns_wide} -neopa", root_symlink="netstat") self.add_cmd_output([ "nstat -zas", "netstat -s", f"netstat {self.ns_wide} -agn", "networkctl status -a", "ip -6 route show table all", "ip -d route show cache", "ip -d -6 route show cache", "ip -4 rule list", "ip -6 rule list", "ip vrf show", "ip -s -d link", "ip -d address", "ifenslave -a", "ip mroute show", "ip maddr show", "ip -s -s neigh show", "ip neigh show nud noarp", "biosdevname -d", "tc -s qdisc show", "nmstatectl show", "nmstatectl show --running-config", ]) if self.path_isdir('/sys/class/devlink'): self.add_cmd_output([ "devlink dev param show", "devlink dev info", "devlink port show", ]) devlinks = self.collect_cmd_output("devlink dev") if devlinks['status'] == 0: devlinks_list = devlinks['output'].splitlines() for devlink in devlinks_list: self.add_cmd_output(f"devlink dev eswitch show {devlink}") # below commands require some kernel module(s) to be loaded # run them only if the modules are loaded, or if explicitly requested # via --allow-system-changes option ip_macsec_show_cmd = "ip -s macsec show" macsec_pred = SoSPredicate(self, kmods=['macsec']) self.add_cmd_output(ip_macsec_show_cmd, pred=macsec_pred, changes=True) self.collect_ss_ip_ethtool_info() self.collect_bridge_info() def add_command_tags(self): """ Command tags for ip/ethtool/netstat """ for opt in self.ethtool_shortopts: self.add_cmd_tags({ f'ethtool -{opt} .*': f'ethool_{opt}' }) self.add_cmd_tags({ "ethtool [^-].*": "ethtool", "ip -d address": "ip_addr", "ip -s -s neigh show": "ip_neigh_show", "ip -s -d link": "ip_s_link", "netstat.*-neopa": "netstat", "netstat.*-agn": "netstat_agn", "netstat -s": "netstat_s" }) def collect_bridge_info(self): """ Collect information about bridges (some data already collected via "ip .." commands) """ self.add_cmd_output([ "bridge -s -s -d link show", "bridge -s -s -d -t fdb show", "bridge -s -s -d -t mdb show", "bridge -d vlan show" ]) def collect_ss_ip_ethtool_info(self): """ Collect ss, ip and ethtool cmd outputs """ ss_cmd = "ss -peaonmi" ss_pred = SoSPredicate(self, kmods=self.ss_kmods, required={'kmods': 'all'}) self.add_cmd_output(ss_cmd, pred=ss_pred, changes=True) # Get ethtool output for every device that does not exist in a # namespace. _ecmds = [f"ethtool -{opt}" for opt in self.ethtool_shortopts] self.add_device_cmd([ _cmd + " %(dev)s" for _cmd in _ecmds ], devices='ethernet') self.add_device_cmd([ "ethtool %(dev)s", "ethtool --phy-statistics %(dev)s", "ethtool --show-priv-flags %(dev)s", "ethtool --show-eee %(dev)s", "tc -s filter show dev %(dev)s", "tc -s filter show dev %(dev)s ingress", ], devices="ethernet") # skip EEPROM collection by default, as it might hang or # negatively impact the system on some device types if self.get_option("eepromdump"): cmd = "ethtool -e %(dev)s" self._log_warn("WARNING: collecting an eeprom dump is known to " "cause certain NIC drivers (e.g. bnx2x/tg3) to " "interrupt device operation") self.add_device_cmd(cmd, devices="ethernet") if self.get_option("traceroute"): self.add_cmd_output(f"/bin/traceroute -n {self.trace_host}", priority=100) # Capture additional data from namespaces; each command is run # per-namespace. self.add_cmd_output("ip netns") cmd_prefix = "ip netns exec " namespaces = self.get_network_namespaces( self.get_option("namespace-pattern"), self.get_option("namespaces")) if namespaces: # 'ip netns exec iptables-save' must be guarded by nf_tables # kmod, if 'iptables -V' output contains 'nf_tables' # analogously for ip6tables cout = {'cmd': 'iptables -V', 'output': 'nf_tables'} co6 = {'cmd': 'ip6tables -V', 'output': 'nf_tables'} iptables_with_nft = (SoSPredicate(self, kmods=['nf_tables']) if self.test_predicate(self, pred=SoSPredicate(self, cmd_outputs=cout)) else None) ip6tables_with_nft = (SoSPredicate(self, kmods=['nf_tables']) if self.test_predicate(self, pred=SoSPredicate(self, cmd_outputs=co6)) else None) for namespace in namespaces: _devs = self.devices['namespaced_network'][namespace] _subdir = f"namespaces/{namespace}" ns_cmd_prefix = cmd_prefix + namespace + " " self.add_cmd_output([ ns_cmd_prefix + "ip -d address show", ns_cmd_prefix + "ip route show table all", ns_cmd_prefix + "ip -s -s neigh show", ns_cmd_prefix + "ip -4 rule list", ns_cmd_prefix + "ip -6 rule list", ns_cmd_prefix + "ip vrf show", ns_cmd_prefix + "sysctl -a", ns_cmd_prefix + f"netstat {self.ns_wide} -neopa", ns_cmd_prefix + "netstat -s", ns_cmd_prefix + f"netstat {self.ns_wide} -agn", ns_cmd_prefix + "nstat -zas", ], priority=50, subdir=_subdir) self.add_cmd_output([ns_cmd_prefix + "iptables-save"], pred=iptables_with_nft, subdir=_subdir, priority=50) self.add_cmd_output([ns_cmd_prefix + "ip6tables-save"], pred=ip6tables_with_nft, subdir=_subdir, priority=50) ss_cmd = ns_cmd_prefix + "ss -peaonmi" # --allow-system-changes is handled directly in predicate # evaluation, so plugin code does not need to separately # check for it self.add_cmd_output(ss_cmd, pred=ss_pred, subdir=_subdir) # Collect ethtool commands only when ethtool_namespaces # is set to true. if self.get_option("ethtool-namespaces"): # Devices that exist in a namespace use less ethtool # parameters. Run this per namespace. self.add_device_cmd([ ns_cmd_prefix + "ethtool %(dev)s", ns_cmd_prefix + "ethtool -i %(dev)s", ns_cmd_prefix + "ethtool -k %(dev)s", ns_cmd_prefix + "ethtool -S %(dev)s" ], devices=_devs['ethernet'], priority=50, subdir=_subdir) self.add_command_tags() class RedHatNetworking(Networking, RedHatPlugin): trace_host = "rhn.redhat.com" def setup(self): # Handle change from -T to -W in Red Hat netstat 2.0 and greater. try: netstat_pkg = self.policy.package_manager.pkg_by_name('net-tools') # major version if int(netstat_pkg['version'][0]) < 2: self.ns_wide = "-T" except Exception: # pylint: disable=broad-except # default to upstream option pass super().setup() class UbuntuNetworking(Networking, UbuntuPlugin, DebianPlugin): trace_host = "archive.ubuntu.com" def setup(self): ubuntu_jammy_and_after_ss_kmods = ['tcp_diag', 'udp_diag', 'inet_diag', 'unix_diag', 'netlink_diag', 'af_packet_diag', 'xsk_diag', 'mptcp_diag', 'raw_diag'] if self.policy.dist_version() >= 22.04: self.ss_kmods = ubuntu_jammy_and_after_ss_kmods super().setup() self.add_copy_spec([ "/etc/netplan/*.yaml", "/etc/network/interfaces", "/etc/network/interfaces.d", "/etc/resolv.conf", "/etc/resolvconf", "/lib/netplan/*.yaml", "/run/netplan/*.yaml", "/run/systemd/network" ]) def postproc(self): self.do_path_regex_sub( "/etc/netplan", r"(\s+password:).*", r"\1 ******" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/hardware.py0000664000175000017500000000221214660147624016733 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Hardware(Plugin, IndependentPlugin): short_desc = 'General hardware information' plugin_name = "hardware" profiles = ('system', 'hardware') def setup(self): self.add_copy_spec("/proc/interrupts", tags='interrupts') self.add_copy_spec([ "/proc/device-tree/compatible", "/proc/device-tree/model", "/proc/irq", "/proc/dma", "/proc/devices", "/proc/rtc", "/var/log/mcelog", "/sys/class/dmi/id/*", "/sys/class/drm/*/edid" ]) self.add_cmd_output("dmidecode", root_symlink="dmidecode", tags="dmidecode") self.add_cmd_output("lshw") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/mysql.py0000664000175000017500000000776314660147624016323 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Mysql(Plugin): short_desc = 'MySQL and MariaDB RDBMS' plugin_name = "mysql" profiles = ('services',) mysql_cnf = "/etc/my.cnf" pw_warn_text = " (password visible in process listings)" option_list = [ PluginOpt('dbuser', default='mysql', val_type=str, desc='username for database dump collection'), PluginOpt('dbpass', default='', val_type=str, desc='password for data dump collection' + pw_warn_text), PluginOpt('dbdump', default=False, desc='Collect a database dump') ] def setup(self): super().setup() self.add_copy_spec([ self.mysql_cnf, "/etc/mysqlrouter/", "/var/lib/mysql/grastate.dat", "/var/lib/mysql/gvwstate.dat" ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/mysql*", "/var/log/mariadb*", "/var/log/mysqlrouter/*" ]) else: self.add_copy_spec([ # Required for MariaDB under pacemaker (MariaDB-Galera) "/var/log/mysqld.log", "/var/log/mysql/mysqld.log", "/var/log/mysqlrouter/mysqlrouter.log", "/var/log/mariadb/mariadb.log" ]) if self.get_option("dbdump"): msg = "database user name and password must be supplied" dbdump_err = f"mysql.dbdump: {msg}" dbuser = self.get_option("dbuser") dbpass = self.get_option("dbpass") if 'MYSQL_PWD' in os.environ: dbpass = os.environ['MYSQL_PWD'] if dbuser is True or dbpass is True: # sos report -a or -k mysql.{dbuser,dbpass} self.soslog.warning(dbdump_err) return if not dbpass or dbpass is False: # no MySQL password self.soslog.warning(dbdump_err) return # no need to save/restore as this variable is private to # the mysql plugin. os.environ['MYSQL_PWD'] = dbpass opts = f"--user={dbuser} --all-databases" name = "mysqldump_--all-databases" self.add_cmd_output(f"mysqldump {opts}", suggest_filename=name) self.add_cmd_output("du -s /var/lib/mysql/*") class RedHatMysql(Mysql, RedHatPlugin): packages = ( 'mysql-server', 'mysql', 'mariadb-server', 'mariadb', 'openstack-selinux' ) def setup(self): super().setup() self.add_copy_spec([ "/etc/ld.so.conf.d/mysql-*.conf", "/etc/ld.so.conf.d/mariadb-*.conf", "/etc/my.cnf.d/*", "/var/lib/config-data/puppet-generated/mysql/etc/my.cnf.d/*" ]) class DebianMysql(Mysql, DebianPlugin, UbuntuPlugin): packages = ( 'mysql-server.*', 'mysql-common', 'mariadb-server.*', 'mariadb-common', 'percona-xtradb-cluster-server-.*', ) def setup(self): super().setup() self.add_copy_spec([ "/etc/mysql/", "/var/log/mysql/error.log", "/var/lib/mysql/*.err", "/var/lib/percona-xtradb-cluster/*.err", "/var/lib/percona-xtradb-cluster/grastate.dat", "/var/lib/percona-xtradb-cluster/gvwstate.dat", "/var/lib/percona-xtradb-cluster/innobackup.*.log", ]) self.add_cmd_output("du -s /var/lib/percona-xtradb-cluster/*") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ostree.py0000664000175000017500000000206614660147624016446 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class OSTree(Plugin, IndependentPlugin): short_desc = 'OSTree' plugin_name = 'ostree' profiles = ('system', 'sysmgmt', 'packagemanager') files = ('/ostree',) services = ('ostree-finalize-staged', 'ostree-boot-complete') option_list = [ PluginOpt('fsck', default=False, desc='collect ostree fsck') ] def setup(self): self.add_copy_spec("/ostree/repo/config") self.add_cmd_output([ "ostree admin status", "ostree admin config-diff", "ostree refs", ]) if self.get_option("fsck"): self.add_cmd_output("ostree fsck") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_ansible.py0000664000175000017500000000167414660147624017565 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin class CephAnsible(Plugin, RedHatPlugin, DebianPlugin): short_desc = 'CEPH distributed storage - Ansible installer' plugin_name = 'ceph_ansible' profiles = ('storage', 'ceph') packages = ('ceph-ansible',) def setup(self): self.add_copy_spec([ "/usr/share/ceph-ansible/group_vars/", "/usr/share/ceph-ansible/site*.yml", "/usr/share/ceph-ansible/ansible.cfg" ]) self.add_forbidden_path("/usr/share/ceph-ansible/group_vars/*.sample") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_cinder.py0000664000175000017500000001553714660147624020467 0ustar arifarif# Copyright (C) 2009 Red Hat, Inc., Joey Boggs # Copyright (C) 2012 Rackspace US, Inc., # Justin Shepherd # Copyright (C) 2013 Red Hat, Inc., Flavio Percoco # Copyright (C) 2013 Red Hat, Inc., Jeremy Agee # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackCinder(Plugin): short_desc = 'OpenStack cinder' plugin_name = "openstack_cinder" profiles = ('openstack', 'openstack_controller') containers = ('.*cinder_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/cinder" apachepkg = None def setup(self): self.add_forbidden_path('/etc/cinder/volumes') cinder_config = "" cinder_config_opt = "--config-dir %s/etc/cinder/" # check if either standalone (cinder-api) or httpd wsgi (cinder_wsgi) # is up and running cinder_process = ["cinder_wsgi", "cinder-wsgi", "cinder-api"] in_ps = False for process in cinder_process: in_ps = self.check_process_by_name(process) if in_ps: break in_container = self.container_exists('.*cinder_api') if in_container: cinder_config = cinder_config_opt % self.var_puppet_gen # collect commands output if the standalone, wsgi or container is up if in_ps or in_container: self.add_cmd_output( "cinder-manage " + cinder_config + " db version", suggest_filename="cinder_db_version" ) self.add_cmd_output( f"cinder-manage {cinder_config} backup list" ) self.add_cmd_output( f"cinder-manage {cinder_config} config list" ) self.add_cmd_output( f"cinder-manage {cinder_config} host list" ) self.add_cmd_output( f"cinder-status {cinder_config} upgrade check" ) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. " "Source the environment file for the user " "intended to connect to the OpenStack " "environment.") else: list_cmds = [ "backend pool", "group type", "message", "qos", "service", "type", ] for cmd in list_cmds: self.add_cmd_output(f"openstack volume {cmd} list") list_cmds_projects = [ "backup", "group", "group snapshot", "snapshot", "transfer request", "", ] for cmd in list_cmds_projects: self.add_cmd_output( f"openstack volume {cmd} list --all-projects" ) # get details for each volume cmd = "openstack volume list -f value --all-projects" res = self.exec_cmd(cmd) if res['status'] == 0: cinder_volumes = res['output'] for volume in cinder_volumes.splitlines(): volume = volume.split()[0] cmd = f"openstack volume show {volume}" self.add_cmd_output(cmd) self.add_forbidden_path('/etc/cinder/volumes') self.add_copy_spec([ "/etc/cinder/", self.var_puppet_gen + "/etc/cinder/", self.var_puppet_gen + "/etc/httpd/conf/", self.var_puppet_gen + "/etc/httpd/conf.d/", self.var_puppet_gen + "/etc/httpd/conf.modules.d/*.conf", self.var_puppet_gen + "/etc/my.cnf.d/tripleo.cnf", self.var_puppet_gen + "/etc/sysconfig/", ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/cinder/", f"/var/log/{self.apachepkg}*/cinder*", ]) else: self.add_copy_spec([ "/var/log/cinder/*.log", f"/var/log/{self.apachepkg}*/cinder*.log", ]) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/cinder/*", regexp, subst) self.do_path_regex_sub( self.var_puppet_gen + "/etc/cinder/*", regexp, subst ) def postproc(self): protect_keys = [ "admin_password", "backup_tsm_password", "chap_password", "nas_password", "cisco_fc_fabric_password", "coraid_password", "eqlx_chap_password", "fc_fabric_password", "hitachi_auth_password", "hitachi_horcm_password", "hp3par_password", "hplefthand_password", "memcache_secret_key", "netapp_password", "netapp_sa_password", "nexenta_password", "password", "qpid_password", "rabbit_password", "san_password", "ssl_key_password", "vmware_host_password", "zadara_password", "zfssa_initiator_password", "hmac_keys", "zfssa_target_password", "os_privileged_user_password", "transport_url" ] connection_keys = ["connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianCinder(OpenStackCinder, DebianPlugin, UbuntuPlugin): cinder = False apachepkg = 'apache2' packages = ( 'cinder-api', 'cinder-backup', 'cinder-common', 'cinder-scheduler', 'cinder-volume', 'python-cinder', 'python3-cinder', ) class RedHatCinder(OpenStackCinder, RedHatPlugin): cinder = False apachepkg = 'httpd' packages = ('openstack-selinux',) def setup(self): super().setup() self.add_copy_spec(["/etc/sudoers.d/cinder"]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/chrony.py0000664000175000017500000000304414660147624016444 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Chrony(Plugin): short_desc = 'Chrony clock (for Network time protocol)' plugin_name = "chrony" profiles = ('system', 'services') packages = ('chrony',) def setup(self): self.add_cmd_output([ "chronyc activity", "chronyc tracking", "chronyc sourcestats", "chronyc serverstats", "chronyc ntpdata", "chronyc -n clients" ]) self.add_cmd_output("chronyc -n sources", tags="chronyc_sources") class RedHatChrony(Chrony, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec([ "/etc/chrony.conf", "/var/lib/chrony/drift" ]) self.add_journal(units="chronyd") class DebianChrony(Chrony, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec([ "/etc/chrony/chrony.conf", "/etc/chrony/conf.d", "/etc/chrony/sources.d", "/var/lib/chrony/chrony.drift", "/etc/default/chrony" ]) self.add_journal(units="chrony") # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/openssl.py0000664000175000017500000000263414660147624016631 0ustar arifarif# Copyright (C) 2007 Sadique Puthen # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenSSL(Plugin): short_desc = 'OpenSSL configuration' plugin_name = "openssl" profiles = ('network', 'security') packages = ('openssl',) verify_packages = ('openssl.*',) def postproc(self): protect_keys = [ "input_password", "output_password", "challengePassword" ] regexp = fr"^(\s*#?\s*({'|'.join(protect_keys)}).*=)(.*)" self.do_file_sub( '/etc/ssl/openssl.cnf', regexp, r"\1 ******" ) class RedHatOpenSSL(OpenSSL, RedHatPlugin): files = ('/etc/pki/tls/openssl.cnf',) def setup(self): super().setup() self.add_copy_spec("/etc/pki/tls/openssl.cnf") class DebianOpenSSL(OpenSSL, DebianPlugin, UbuntuPlugin): files = ('/etc/ssl/openssl.cnf',) def setup(self): super().setup() self.add_copy_spec("/etc/ssl/openssl.cnf") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/fibrechannel.py0000664000175000017500000000342014660147624017560 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Fibrechannel(Plugin, RedHatPlugin): short_desc = 'Collect information on fibrechannel devices' plugin_name = 'fibrechannel' profiles = ('hardware', 'storage', 'system') files = ('/sys/class/fc_host', '/sys/class/fc_remote_ports') option_list = [ PluginOpt('debug', default=True, desc='collect debugging logs') ] # vendor specific debug paths debug_paths = [ '/sys/kernel/debug/qla2*/', '/sys/kernel/debug/fnic/', ] def setup(self): self.add_device_cmd("udevadm info -a %(dev)s", devices='fibre') if self.get_option('debug'): self.add_copy_spec(self.debug_paths) self.add_cmd_output([ "hbacmd listhbas", "hbacmd ServerAttributes" ]) # collect Hbaattributes and Portattributes of WWN listhbas = self.collect_cmd_output("hbacmd listhbas") if listhbas['status'] == 0: for line in listhbas['output'].splitlines(): if 'Port WWN' in line: dev = line.split()[3] self.add_cmd_output([ f"hbacmd HbaAttributes {dev}", f"hbacmd PortAttributes {dev}", f"hbacmd GetXcvrData {dev}", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cups.py0000664000175000017500000000231314660147624016112 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Cups(Plugin, IndependentPlugin): short_desc = 'CUPS IPP print service' plugin_name = 'cups' profiles = ('hardware',) services = ('cups', 'cups-browsed') packages = ('cups',) def setup(self): if not self.get_option("all_logs"): self.add_copy_spec("/var/log/cups/access_log") self.add_copy_spec("/var/log/cups/error_log") self.add_copy_spec("/var/log/cups/page_log") else: self.add_copy_spec("/var/log/cups") self.add_copy_spec([ "/etc/cups/*.conf", "/etc/cups/*.types", "/etc/cups/lpoptions", "/etc/cups/ppd/*.ppd" ]) self.add_cmd_output([ "lpstat -t", "lpstat -s", "lpstat -d" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nfs.py0000664000175000017500000000251014660147624015725 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Nfs(Plugin, IndependentPlugin): short_desc = 'Network file system information' plugin_name = 'nfs' profiles = ('storage', 'network', 'nfs') packages = ('nfs-utils', ) def setup(self): self.add_copy_spec([ "/etc/nfsmount.conf", "/etc/idmapd.conf", "/etc/nfs.conf", "/proc/fs/nfsfs/servers", "/proc/fs/nfsfs/volumes", "/run/sysconfig/nfs-utils", "/etc/exports", "/etc/exports.d", "/var/lib/nfs/etab", "/var/lib/nfs/xtab", "/var/lib/nfs/rmtab", "/proc/fs/nfsd", ]) self.add_cmd_output([ "nfsstat -o all", "exportfs -v", "nfsdclnts", "nfsconf -d", "mountstats -n", "mountstats -r", "mountstats -x", "rpcctl xprt show", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/canonical_livepatch.py0000664000175000017500000000204414660147624021127 0ustar arifarif# Copyright (c) 2016 Bryan Quigley # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class CanonicaLivepatch(Plugin, UbuntuPlugin): short_desc = 'Canonical Livepatch Service' plugin_name = 'canonical_livepatch' profiles = ('system', 'kernel') commands = ('canonical-livepatch',) services = ('snap.canonical-livepatch.canonical-livepatchd',) def setup(self): self.add_cmd_output([ "canonical-livepatch status --verbose", "canonical-livepatch --version", "canonical-livepatch config", ]) def postproc(self): self.do_cmd_private_sub("canonical-livepatch config") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/mvcli.py0000664000175000017500000000175314660147624016261 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class MvCLI(Plugin, IndependentPlugin): """ The mvCLI plugin is meant for sas adapters, and collects information for each adapter discovered on the system. """ short_desc = 'mvCLI Integrated RAID adapter information' plugin_name = "mvcli" commands = ("/opt/marvell/bin/mvcli",) def setup(self): # get list of adapters subcmds = [ 'info -o vd', 'info -o pd', 'info -o hba', 'smart -p 0', ] self.add_cmd_output([f"/opt/marvell/bin/mvcli {s}" for s in subcmds]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/host.py0000664000175000017500000000337114660147624016122 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Host(Plugin, IndependentPlugin): """This plugin primarily collects hostname related information, as well as a few collections that do not fit well in other plugins. For example, uptime information and SoS configuration data from /etc/sos. This plugin is not intended to be a catch-all "general" plugin however for these types of collections that do not have a specific component/package or pre-existing plugin. """ short_desc = 'Host information' plugin_name = 'host' profiles = ('system',) def setup(self): self.add_forbidden_path('/etc/sos/cleaner') self.add_cmd_output('hostname', root_symlink='hostname', tags=['hostname_default', 'hostname_short']) self.add_cmd_output('hostname -f', tags='hostname') self.add_cmd_output('uptime', root_symlink='uptime', tags="uptime") self.add_cmd_output('find / -maxdepth 2 -type l -ls', root_symlink='root-symlinks') self.add_cmd_output([ 'hostid', 'hostnamectl status' ]) self.add_copy_spec([ '/etc/sos', '/etc/hostid', ]) self.add_env_var([ 'REMOTEHOST', 'TERM', 'COLORTERM' ]) sos-4.8.0/sos/report/plugins/sar.py0000664000175000017500000000771714660147624015742 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from datetime import datetime as dt import os import re from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Sar(Plugin): """ The sar plugin is designed to collect system performance data as recorded by sysstat. The raw binary data, i.e. the 'saX' files, will be collected and for files a week old or younger, this plugin will capture human-readable conversions of those files provided by the 'sar' command locally available, if the local sysstat installation has not already created a converted copy (e.g. for the current day-of data being collected at the time of report generation). Using the 'all-sar' plugin option will not only cause the plugin to capture _all_ 'saX' files present on the host, but further perform the 'sar' conversion on all files, not just those produced within the last week. Converted 'sar' files will be written to the sos_commands/sar/, and not to the /var/log/ path that sysstat writes to. Note that this conversion is done because it is unlikely that the same version of sysstat that produces the 'saX' files will be the same version available on a given analyst's workstation, and this conversion is version sensitive. """ short_desc = 'System Activity Reporter' plugin_name = 'sar' profiles = ('system', 'performance') packages = ('sysstat',) sa_path = '/var/log/sa' option_list = [ PluginOpt('all-sar', default=False, desc="gather all system activity records") ] def setup(self): self.add_copy_spec(self.path_join(self.sa_path, '*'), sizelimit=0 if self.get_option("all-sar") else None, tailit=False) try: dir_list = self.listdir(self.sa_path) except OSError: self._log_warn(f"sar: could not list {self.sa_path}") return sa_regex = re.compile(r"sa[\d]+") # find all the sa files that don't have an existing sar file # there are two possible formats for sar files # saDD, the default one where DD is the day of the month # saYYYYMMDD, which is the format when specifying -D # as option for sadc for fname in dir_list: if sa_regex.match(fname): sa_data_path = self.path_join(self.sa_path, fname) sar_filename = 'sar' + fname[2:] if sar_filename not in dir_list: # only collect sar output for the last 7 days by default if not self.get_option('all-sar') and \ self.is_older_than_7days(sa_data_path): continue sar_cmd = f"sar -A -f {sa_data_path}" self.add_cmd_output(sar_cmd, sar_filename) sadf_cmd = f"sadf -x -- -A {sa_data_path}" self.add_cmd_output(sadf_cmd, f"{fname}.xml") def is_older_than_7days(self, sarfile): """ Is the file older than 7 days? """ try: _ftime = os.stat(sarfile).st_mtime _age = dt.today() - dt.fromtimestamp(_ftime) if _age.days <= 7: return False except Exception as err: # pylint: disable=broad-except self._log_warn(f"Could not determine age of '{sarfile}' - " f"skipping converting to sar format: {err}") return True class RedHatSar(Sar, RedHatPlugin): sa_path = '/var/log/sa' class DebianSar(Sar, DebianPlugin, UbuntuPlugin): sa_path = '/var/log/sysstat' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/vmware.py0000664000175000017500000000336014660147624016444 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class VMWare(Plugin, RedHatPlugin): short_desc = 'VMWare client information' plugin_name = 'vmware' profiles = ('virt',) packages = ('open-vm-tools', 'VMWare-Tools') files = ('/etc/vmware-tools', '/etc/vmware') commands = ('vmware-toolbox-cmd',) services = ('vmtoolsd',) def setup(self): self.add_copy_spec([ "/etc/vmware-tools/", "/etc/vmware/locations", "/etc/vmware/config", "/proc/vmmemctl", "/sys/kernel/debug/vmmemctl", "/var/log/vmware-network.log", "/var/log/vmware-vgauthsvc.log.0", "/var/log/vmware-vmsvc-root.log", "/var/log/vmware-vmtoolsd-root.log", "/var/log/vmware-vmusr-root.log" ]) self.add_file_tags({ "/etc/vmware-tools/tools.conf": "vmware_tools_conf" }) self.add_cmd_output([ "vmware-checkvm", "vmware-toolbox-cmd device list", "vmware-toolbox-cmd -v", "vmware-toolbox-cmd timesync status" ]) stats = self.exec_cmd("vmware-toolbox-cmd stat raw") if stats['status'] == 0: for _stat in stats['output'].splitlines(): self.add_cmd_output("vmware-toolbox-cmd stat raw text" f" {_stat}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/postgresql.py0000664000175000017500000001056514660147624017353 0ustar arifarif# Copyright (C) 2017 Red Hat, Inc., Pavel Moravec # Copyright (C) 2014 Red Hat, Inc., Sandro Bonazzola # Copyright (C) 2013 Chris J Arges # Copyright (C) 2012-2013 Red Hat, Inc., Bryn M. Reeves # Copyright (C) 2011 Red Hat, Inc., Jesse Jaggars # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import (Plugin, UbuntuPlugin, DebianPlugin, RedHatPlugin, PluginOpt) from sos.utilities import find class PostgreSQL(Plugin): short_desc = 'PostgreSQL RDBMS' plugin_name = "postgresql" profiles = ('services',) packages = ('postgresql', 'postgresql-common') password_warn_text = " (password visible in process listings)" option_list = [ PluginOpt('pghome', default='/var/lib/pgsql', desc='psql server home directory'), PluginOpt('username', default='postgres', val_type=str, desc='username for pg_dump'), PluginOpt('password', default='', val_type=str, desc='password for pg_dump' + password_warn_text), PluginOpt('dbname', default='', val_type=str, desc='database name to dump with pg_dump'), PluginOpt('dbhost', default='', val_type=str, desc='database hostname/IP address (no unix sockets)'), PluginOpt('dbport', default=5432, val_type=int, desc='database server listening port') ] def do_pg_dump(self, filename="pgdump.tar"): """ Extract PostgreSQL database into a tar file """ if self.get_option("dbname"): if self.get_option("password") or "PGPASSWORD" in os.environ: # We're only modifying this for ourself and our children so # there is no need to save and restore environment variables if # the user decided to pass the password on the command line. if self.get_option("password"): os.environ["PGPASSWORD"] = self.get_option("password") if self.get_option("dbhost"): cmd = (f"pg_dump -U {self.get_option('username')} " f"-h {self.get_option('dbhost')} " f"-p {self.get_option('dbport')} -w -F t " f"{self.get_option('dbname')}") else: cmd = (f"pg_dump -C -U {self.get_option('username')} -w " f"-F t {self.get_option('dbname')}") self.add_cmd_output(cmd, suggest_filename=filename, binary=True, sizelimit=0) else: # no password in env or options self.soslog.warning( "password must be supplied to dump a database." ) self.add_alert( "WARN: password must be supplied to dump a database." ) def setup(self): self.do_pg_dump() self.add_cmd_output(f"du -sh {self.get_option('pghome')}") class RedHatPostgreSQL(PostgreSQL, RedHatPlugin): def setup(self): super().setup() pghome = self.get_option("pghome") dirs = [pghome] for _dir in dirs: # Copy PostgreSQL log files. for filename in find("*.log", _dir): self.add_copy_spec(filename) # Copy PostgreSQL config files. for filename in find("*.conf", _dir): self.add_copy_spec(filename) # copy PG_VERSION and postmaster.opts for file in ["PG_VERSION", "postmaster.opts"]: self.add_copy_spec(self.path_join(_dir, "data", file)) class DebianPostgreSQL(PostgreSQL, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec([ "/var/log/postgresql/*.log", "/etc/postgresql/*/main/*.conf", "/var/lib/postgresql/*/main/PG_VERSION", "/var/lib/postgresql/*/main/postmaster.opts" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/systemd.py0000664000175000017500000000701214660147624016631 0ustar arifarif# Copyright (C) 2012 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate from sos.utilities import is_executable class Systemd(Plugin, IndependentPlugin): short_desc = 'System management daemon' plugin_name = "systemd" profiles = ('system', 'services', 'boot') packages = ('systemd',) files = ('/run/systemd/system',) def setup(self): self.add_file_tags({ '/etc/systemd/journald.conf': 'etc_journald_conf', '/usr/lib/systemd/journald.conf': 'usr_journald_conf_d', '/etc/systemd/system.conf': 'systemd_system_conf', '/etc/systemd/logind.conf': 'systemd_logind_conf' }) self.add_dir_listing('/lib/systemd', recursive=True) self.add_cmd_output([ "journalctl --list-boots", "systemctl list-dependencies", "systemctl list-jobs", "systemctl list-machines", "systemctl list-unit-files", "systemctl list-units", "systemctl list-units --all", "systemctl list-units --failed", "systemctl list-timers --all", # It is possible to do systemctl show with target, slice, # device, socket, scope, and mount too but service and # status --all mostly seems to cover the others. "systemctl show --all", "systemctl show *service --all", "systemctl show-environment", "systemctl status --all", "systemd-delta", "systemd-analyze", "systemd-analyze blame", "systemd-analyze dump", "systemd-inhibit --list" ], cmd_as_tag=True) self.add_cmd_output('timedatectl', root_symlink='date') # resolvectl command starts systemd-resolved service if that # is not running, so gate the commands by this predicate if is_executable('resolvectl'): resolvectl_status = 'resolvectl status' resolvectl_statistics = 'resolvectl statistics' else: resolvectl_status = 'systemd-resolve --status' resolvectl_statistics = 'systemd-resolve --statistics' self.add_cmd_output([ resolvectl_status, resolvectl_statistics, ], pred=SoSPredicate(self, services=["systemd-resolved"])) self.add_cmd_output("systemd-analyze plot", suggest_filename="systemd-analyze_plot.svg") if self.get_option("verify"): self.add_cmd_output("journalctl --verify") self.add_copy_spec([ "/etc/systemd", "/lib/systemd/system", "/lib/systemd/user", "/etc/vconsole.conf", "/run/systemd/generator*", "/run/systemd/seats", "/run/systemd/sessions", "/run/systemd/system", "/run/systemd/users", "/etc/modules-load.d/*.conf", "/etc/yum/protected.d/systemd.conf", "/etc/tmpfiles.d/*.conf", "/run/tmpfiles.d/*.conf", "/usr/lib/tmpfiles.d/*.conf", ]) self.add_forbidden_path('/dev/null') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/freeipmi.py0000664000175000017500000000160014660147624016736 0ustar arifarif# Copyright (C) 2020 Canonical Ltd. Arif Ali # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class Freeipmi(Plugin, UbuntuPlugin): short_desc = 'Freeipmi hardware information' plugin_name = 'freeipmi' profiles = ('hardware', 'system', ) packages = ('freeipmi-tools',) def setup(self): self.add_cmd_output([ "bmc-info", "ipmi-sel", "ipmi-sensors", "ipmi-chassis --get-status", "ipmi-fru", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nvme.py0000664000175000017500000000333314660147624016110 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Nvme(Plugin, IndependentPlugin): """Collects nvme device configuration information for each nvme device that is installed on the system. Basic information is collected via the `smartctl` utility, however detailed information will be collected via the `nvme` CLI if the `nvme-cli` package is installed. """ short_desc = 'NVMe device information' plugin_name = "nvme" profiles = ('storage',) packages = ('nvme-cli',) kernel_mods = ('nvme', 'nvme_core') def setup(self): self.add_copy_spec([ "/etc/nvme/*", "/sys/class/nvme-fabrics/ctl/nvme*", "/sys/class/nvme-subsystem/nvme-subsys*/*", "/sys/module/nvme_core/parameters/*", ]) self.add_cmd_output([ "nvme list", "nvme list-subsys", ]) cmds = [ "smartctl --all %(dev)s", "smartctl --all %(dev)s -j", "nvme list-ns %(dev)s", "nvme fw-log %(dev)s", "nvme list-ctrl %(dev)s", "nvme id-ctrl -H %(dev)s", "nvme id-ns -H %(dev)s", "nvme smart-log %(dev)s", "nvme error-log %(dev)s", "nvme show-regs %(dev)s" ] self.add_device_cmd(cmds, devices='block', whitelist='nvme.*') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openvswitch.py0000664000175000017500000004240614660147624017520 0ustar arifarif# Copyright (C) 2014 Adam Stokes # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from os import environ import re from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenVSwitch(Plugin): short_desc = 'OpenVSwitch networking' plugin_name = "openvswitch" profiles = ('network', 'virt') actl = "ovs-appctl" vctl = "ovs-vsctl" ofctl = "ovs-ofctl" dpctl = "ovs-dpctl" check_dpdk = False check_6wind = False def setup(self): all_logs = self.get_option("all_logs") log_dirs = [ '/var/log/openvswitch/', '/usr/local/var/log/openvswitch/', ] dpdk_enabled = self.collect_cmd_output( f"{self.vctl} -t 5 get Open_vSwitch . other_config:dpdk-init") self.check_dpdk = (dpdk_enabled["status"] == 0 and dpdk_enabled["output"].startswith('"true"')) self.check_6wind = any(self.is_installed(p) for p in ['6windgate-fp', 'nuage-openvswitch']) if environ.get('OVS_LOGDIR'): log_dirs.append(environ.get('OVS_LOGDIR')) if not all_logs: self.add_copy_spec([ self.path_join(ld, '*.log') for ld in log_dirs ]) else: self.add_copy_spec(log_dirs) self.add_copy_spec([ "/run/openvswitch/ovsdb-server.pid", "/run/openvswitch/ovs-vswitchd.pid", "/run/openvswitch/ovs-monitor-ipsec.pid" ]) self.add_copy_spec([ self.path_join('/usr/local/etc/openvswitch', 'conf.db'), self.path_join('/etc/openvswitch', 'conf.db'), self.path_join('/var/lib/openvswitch', 'conf.db'), ]) ovs_dbdir = environ.get('OVS_DBDIR') if ovs_dbdir: self.add_copy_spec(self.path_join(ovs_dbdir, 'conf.db')) self.add_file_tags({ "/var/log/openvswitch/ovs-vswitchd.log": "openvswitch_daemon_log", "/var/log/openvswitch/ovsdb-server.log": "openvswitch_server_log" }) self.add_dir_listing([ '/run/openvswitch', '/dev/hugepages/', '/dev/vfio', '/var/lib/vhost_sockets', ]) self.add_cmd_output([ # List devices and their drivers "dpdk_nic_bind --status", "dpdk-devbind.py --status", "driverctl list-devices", "driverctl -v list-devices", "driverctl list-overrides", "driverctl -v list-overrides", "driverctl list-persisted", # Capture a list of all bond devices f"{self.actl} bond/list", # Capture more details from bond devices f"{self.actl} bond/show", # Capture LACP details f"{self.actl} lacp/show", f"{self.actl} lacp/show-stats", # Capture coverage stats" f"{self.actl} coverage/show", # Capture cached routes f"{self.actl} ovs/route/show", # Capture tnl arp table" f"{self.actl} tnl/arp/show", # Capture a list of listening ports" f"{self.actl} tnl/ports/show -v", # Capture upcall information f"{self.actl} upcall/show", # Capture OVS list f"{self.vctl} -t 5 list Open_vSwitch", # Capture OVS interface list f"{self.vctl} -t 5 list interface", # Capture OVS detailed information from all the bridges f"{self.vctl} -t 5 list bridge", # Capture OVS datapath list f"{self.vctl} -t 5 list datapath", # Capture DPDK queue to pmd mapping f"{self.actl} dpif-netdev/pmd-rxq-show -secs 5", f"{self.actl} dpif-netdev/pmd-rxq-show -secs 30", f"{self.actl} dpif-netdev/pmd-rxq-show", # Capture DPDK pmd stats f"{self.actl} dpif-netdev/pmd-stats-show", # Capture DPDK pmd performance counters f"{self.actl} dpif-netdev/pmd-perf-show", # Capture ofproto tunnel configs f"{self.actl} ofproto/list-tunnels", # Capture ipsec tunnel information f"{self.actl} -t ovs-monitor-ipsec tunnels/show", f"{self.actl} -t ovs-monitor-ipsec xfrm/state", f"{self.actl} -t ovs-monitor-ipsec xfrm/policies", # Capture OVS offload enabled flows f"{self.dpctl} dump-flows --name -m type=offloaded", # Capture OVS slowdatapth flows f"{self.dpctl} dump-flows --name -m type=ovs", # Capture dpcls implementations f"{self.actl} dpif-netdev/subtable-lookup-prio-get", # Capture dpif implementations f"{self.actl} dpif-netdev/dpif-impl-get", # Capture miniflow extract implementations f"{self.actl} dpif-netdev/miniflow-parser-get", # Capture DPDK pmd sleep config f"{self.actl} dpif-netdev/pmd-sleep-show", # Capture additional DPDK info f"{self.actl} dpdk/lcore-list", f"{self.actl} dpdk/log-list", f"{self.actl} dpdk/get-malloc-stats", # Capture dpdk mempool info f"{self.actl} netdev-dpdk/get-mempool-info" ]) # Capture DPDK and other parameters self.add_cmd_output( f"{self.vctl} -t 5 get Open_vSwitch . other_config", tags="openvswitch_other_config") # The '-t 5' adds an upper bound on how long to wait to connect # to the Open vSwitch server, avoiding hangs when running sos. self.add_cmd_output(f"{self.vctl} -t 5 show", tags="ovs_vsctl_show") # Gather systemd services logs self.add_journal(units="openvswitch") self.add_journal(units="openvswitch-nonetwork") self.add_journal(units="ovs-vswitchd") self.add_journal(units="ovsdb-server") self.add_journal(units="ovs-configuration") self.add_journal(units="openvswitch-ipsec") self.collect_ovs_info() self.collect_datapath() self.collect_ovs_bridge_info() def collect_ovs_info(self): """ Collect output of OVS commands """ files_6wind = [ "/etc/systemd/system/multi-user.target.wants/openvswitch.service", "/etc/sysctl.d/60-6wind-system-auto-reboot.conf", "/etc/openvswitch/system-id.conf", "/etc/openvswitch/*.db", "/etc/ld.so.conf.d/linux-fp-sync-fptun.conf", "/etc/NetworkManager/conf.d/fpn0.conf", "/etc/default/openvswitch", "/etc/logrotate.d/openvswitch", "/etc/linux-fp-sync.env", "/etc/fp-daemons.env", "/etc/fp-vdev.ini", "/etc/fpm.env", "/etc/6WINDGate/fp.config", "/etc/6WINDGate/fpnsdk.config", "/etc/dms.d/fp-dms.conf", "/etc/dms.d/fpmd-dms.conf", "/etc/dms.d/fpsd-dms.conf", "/etc/fast-path.env", "/etc/fps-fp.env", ] if self.check_6wind: self.add_copy_spec(files_6wind) self.add_cmd_output([ # Various fast-path stats "fp-cli fp-vswitch-stats", "fp-cli dpdk-core-port-mapping", "fp-cpu-usage", "fp-cli fp-vswitch-masks", "fp-cli fp-vswitch-flows", "fp-shmem-dpvi", "fp-cli stats non-zero", "fp-cli stats", "fp-cli dpdk-cp-filter-budget", f"{self.actl} vm/port-detailed-show", f"{self.actl} upcall/show", "fp-cli nfct4", f"{self.actl} vm/port-vip-list-show", "fp-shmem-ports -s", f"{self.dpctl} show -s", "fpcmd fp-vswitch-flows", "fp-cli fp-vswitch-ports percore", "fp-cli dpdk-debug-pool", "fp-cli dump-size", "fp-cli conf runtime", "fp-cli conf compiled", "fp-cli iface", f"{self.actl} memory/show", ]) self.add_journal(units="virtual-accelerator") for table in ['filter', 'mangle', 'raw', 'nat']: self.add_cmd_output([f"fpcmd nf4-rules {table}"]) # 6wind doesn't care on which bridge the ports are, there's only # one bridge and it's alubr0 port_list = self.collect_cmd_output("fp-cli fp-vswitch-ports") if port_list['status'] == 0: for port in port_list['output'].splitlines(): mport = re.match(r'^([\d]+):[\s]+([^\s]+)', port) if mport: port_name = mport.group(2) self.add_cmd_output([ f"fp-cli dpdk-cp-filter-budget {port_name}", ]) def collect_datapath(self): """ Gather the datapath information for each datapath """ dp_list_result = self.collect_cmd_output(f'{self.actl} dpctl/dump-dps') if dp_list_result['status'] == 0: for dps in dp_list_result['output'].splitlines(): self.add_cmd_output([ f"{self.actl} dpctl/show -s {dps}", f"{self.actl} dpctl/dump-flows -m {dps}", f"{self.actl} dpctl/dump-conntrack -m {dps}", f"{self.actl} dpctl/ct-stats-show -m {dps}", f"{self.actl} dpctl/ipf-get-status {dps}", ]) def collect_ovs_bridge_info(self): """ Gather additional output for each OVS bridge on the host. """ br_list_result = self.collect_cmd_output(f"{self.vctl} -t 5 list-br") if br_list_result['status'] != 0: return for bri in br_list_result['output'].splitlines(): self.add_cmd_output([ f"{self.actl} bridge/dump-flows --offload-stats {bri}", f"{self.actl} dpif/show-dp-features {bri}", f"{self.actl} fdb/show {bri}", f"{self.actl} fdb/stats-show {bri}", f"{self.actl} mdb/show {bri}", f"{self.ofctl} dump-flows {bri}", f"{self.ofctl} dump-ports-desc {bri}", f"{self.ofctl} dump-ports {bri}", f"{self.ofctl} queue-get-config {bri}", f"{self.ofctl} queue-stats {bri}", f"{self.ofctl} show {bri}", f"{self.ofctl} dump-groups {bri}", ]) self.get_flow_versions(bri) self.get_port_list(bri) if self.check_dpdk: iface_list_result = self.exec_cmd( f"{self.vctl} -t 5 list-ifaces {bri}" ) if iface_list_result['status'] == 0: for iface in iface_list_result['output'].splitlines(): self.add_cmd_output( f"{self.actl} netdev-dpdk/get-mempool-info {iface}" ) if self.check_6wind: self.add_cmd_output([ f"{self.actl} evpn/vip-list-show {bri}", f"{self.actl} bridge/dump-conntracks-summary {bri}", f"{self.actl} bridge/acl-table ingress/egress {bri}", f"{self.actl} bridge/acl-table {bri}", f"{self.actl} ofproto/show {bri}", ]) vrf_list = self.collect_cmd_output( f"{self.actl} vrf/list {bri}") if vrf_list['status'] == 0: vrfs = vrf_list['output'].split()[1:] for vrf in vrfs: self.add_cmd_output([ f"{self.actl} vrf/route-table {vrf}", ]) evpn_list = self.collect_cmd_output( f"{self.actl} evpn/list {bri}") if evpn_list['status'] == 0: evpns = evpn_list['output'].split()[1:] for evpn in evpns: self.add_cmd_output([ f"{self.actl} evpn/mac-table {evpn}", f"{self.actl} evpn/arp-table {evpn}", f"{self.actl} evpn/dump-flows {bri} {evpn}", f"{self.actl} evpn/dhcp-pool-show {bri} {evpn}", f"{self.actl} evpn/dhcp-relay-show {bri} {evpn}", f"{self.actl} evpn/dhcp-static-show {bri} {evpn}", f"{self.actl} evpn/dhcp-table-show {bri} {evpn}", f"{self.actl} evpn/proxy-arp-filter-list " f"{bri} {evpn}", f"{self.actl} evpn/show {bri} {evpn}", f"{self.actl} port/dscp-table {bri} {evpn}", ]) def get_flow_versions(self, bridge): """ Collect flow version of the given bridge """ # Flow protocols currently supported flow_versions = [ "OpenFlow10", "OpenFlow11", "OpenFlow12", "OpenFlow13", "OpenFlow14", "OpenFlow15" ] # Flow protocol hex identifiers ofp_versions = { 0x01: "OpenFlow10", 0x02: "OpenFlow11", 0x03: "OpenFlow12", 0x04: "OpenFlow13", 0x05: "OpenFlow14", 0x06: "OpenFlow15", } ofp_ver_result = self.collect_cmd_output(f"{self.vctl} -t 5 --version") # List protocols currently in use, if any br_info = self.collect_cmd_output( f"{self.vctl} -t 5 list bridge {bridge}") br_protos = [] for line in br_info['output'].splitlines(): if "protocols" in line: br_protos_ln = line[line.find("[")+1:line.find("]")] br_protos = br_protos_ln.replace('"', '').split(", ") # If 'list bridge' yeilded no protocols, use the range of # protocols enabled by default on this version of ovs. if br_protos == [''] and ofp_ver_result['output']: ofp_version_range = ofp_ver_result['output'].splitlines() ver_range = [] for line in ofp_version_range: if "OpenFlow versions" in line: ver_sp = line.split("OpenFlow versions ") ver = ver_sp[1].split(":") ver_range = range(int(ver[0], 16), int(ver[1], 16)+1) for protocol in ver_range: if protocol in ofp_versions: br_protos.append(ofp_versions[protocol]) # Collect flow information for relevant protocol versions only for flow in flow_versions: if flow in br_protos: self.add_cmd_output([ f"{self.ofctl} -O {flow} show {bridge}", f"{self.ofctl} -O {flow} dump-groups {bridge}", f"{self.ofctl} -O {flow} dump-group-stats {bridge}", f"{self.ofctl} -O {flow} dump-flows {bridge}", f"{self.ofctl} -O {flow} dump-tlv-map {bridge}", f"{self.ofctl} -O {flow} dump-ports-desc {bridge}", ]) def get_port_list(self, bridge): """ Collect port list of the given bridge """ port_list_result = self.exec_cmd( f"{self.vctl} -t 5 list-ports {bridge}") if port_list_result['status'] == 0: for port in port_list_result['output'].splitlines(): self.add_cmd_output([ f"{self.actl} cfm/show {port}", f"{self.actl} qos/show {port}", # Not all ports are "bond"s, but all "bond"s are # a single port f"{self.actl} bond/show {port}", # In the case of IPSec, we should pull the config f"{self.actl} get Interface {port} options", ]) if self.check_dpdk: self.add_cmd_output( f"{self.actl} netdev-dpdk/get-mempool-info {port}") class RedHatOpenVSwitch(OpenVSwitch, RedHatPlugin): packages = ('openvswitch', 'openvswitch[2-9].*', 'openvswitch-dpdk', 'nuage-openvswitch' '6windgate-fp') class DebianOpenVSwitch(OpenVSwitch, DebianPlugin, UbuntuPlugin): packages = ('openvswitch-switch', 'nuage-openvswitch') files = ( '/var/snap/openstack-hypervisor/common/etc/openvswitch/system-id.conf', ) def setup(self): if self.is_installed('openstack-hypervisor'): self.ovs_cmd_pre = "openstack-hypervisor." self.actl = f"{self.ovs_cmd_pre}{self.actl}" self.vctl = f"{self.ovs_cmd_pre}{self.vctl}" self.ofctl = f"{self.ovs_cmd_pre}{self.ofctl}" self.dpctl = f"{self.ovs_cmd_pre}{self.dpctl}" super().setup() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/networkmanager.py0000664000175000017500000001173014660147624020167 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class NetworkManager(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'NetworkManager service configuration' plugin_name = 'networkmanager' profiles = ('network', 'hardware', 'system') packages = ('NetworkManager', 'network-manager') def setup(self): self.add_copy_spec([ "/etc/NetworkManager/NetworkManager.conf", "/etc/NetworkManager/system-connections", "/etc/NetworkManager/dispatcher.d", "/etc/NetworkManager/conf.d", "/usr/lib/NetworkManager/conf.d", "/run/NetworkManager/conf.d", "/var/lib/NetworkManager/NetworkManager-intern.conf" ]) self.add_journal(units="NetworkManager") self.add_cmd_output("NetworkManager --print-config") # There are some incompatible changes in nmcli since # the release of NetworkManager >= 0.9.9. In addition, # NetworkManager >= 0.9.9 will use the long names of # "nmcli" objects. # All versions conform to the following templates with different # strings for the object being operated on. nmcli_con_details_template = "nmcli con %s id" nmcli_dev_details_template = "nmcli dev %s" # test NetworkManager status for the specified major version def test_nm_status(version=1): status_template = "nmcli --terse --fields RUNNING %s status" obj_table = [ "nm", # < 0.9.9 "general" # >= 0.9.9 ] status = self.exec_cmd(status_template % obj_table[version]) return (status['status'] == 0 and status['output'].lower().startswith("running")) # NetworkManager >= 0.9.9 (Use short name of objects for nmcli) if test_nm_status(version=1): self.add_cmd_output([ "nmcli general status", "nmcli con", "nmcli -f all con", "nmcli con show --active", "nmcli dev"]) nmcli_con_details_cmd = nmcli_con_details_template % "show" nmcli_dev_details_cmd = nmcli_dev_details_template % "show" # NetworkManager < 0.9.9 (Use short name of objects for nmcli) elif test_nm_status(version=0): self.add_cmd_output([ "nmcli nm status", "nmcli con", "nmcli con status", "nmcli dev"]) nmcli_con_details_cmd = nmcli_con_details_template % "list id" nmcli_dev_details_cmd = nmcli_dev_details_template % "list iface" # No grokkable NetworkManager version present else: nmcli_con_details_cmd = "" nmcli_dev_details_cmd = "" if len(nmcli_con_details_cmd) > 0: nmcli_con_show_result = self.exec_cmd( "nmcli --terse --fields NAME con" ) if nmcli_con_show_result['status'] == 0: for con in nmcli_con_show_result['output'].splitlines(): if con[0:7] == 'Warning': continue # nm names may contain embedded quotes (" and '). These # will cause an exception in shlex.split() if the quotes # are unbalanced. This may happen with names like: # "Foobar's Wireless Network". Although the problem will # occur for both single and double quote characters the # former is considerably more likely in object names since # it is syntactically valid in many human languages. # # Reverse the normal sos quoting convention here and place # double quotes around the innermost quoted string. self.add_cmd_output(f'{nmcli_con_details_cmd} "{con}"') self.add_device_cmd( nmcli_dev_details_cmd + ' "%(dev)s"', devices='ethernet' ) self.add_cmd_tags({ "nmcli dev show": "nmcli_dev_show", "nmcli dev show .*": "nmcli_dev_show_sos" }) def postproc(self): for _, _, files in os.walk( "/etc/NetworkManager/system-connections"): for net_conf in files: self.do_file_sub( "/etc/NetworkManager/system-connections/"+net_conf, r"(password|psk|mka-cak|password-raw|pin|preshared-key" r"|private-key|secrets|wep-key[0-9])=(.*)", r"\1=***", ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/fwupd.py0000664000175000017500000000264214660147624016272 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate class Fwupd(Plugin, IndependentPlugin): short_desc = 'fwupd information' plugin_name = 'fwupd' profiles = ('system', ) services = ('fwupd',) packages = ('fwupd',) def setup(self): self.set_cmd_predicate(SoSPredicate(self, services=["fwupd"])) self.add_cmd_output([ "fwupdmgr get-approved-firmware", "fwupdmgr get-devices --no-unreported-check", "fwupdmgr get-history", "fwupdmgr get-remotes", # collect json format using fwupdagent "/usr/libexec/fwupd/fwupdagent get-devices", "/usr/libexec/fwupd/fwupdagent get-updates", ]) self.add_copy_spec("/etc/fwupd") def postproc(self): self.do_path_regex_sub( "/etc/fwupd/remotes.d/*", r"(Password)=(.*)", r"\1=********" ) self.do_file_sub( "/etc/fwupd/redfish.conf", r"(Password)=(.*)", r"\1=********" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/arcconf.py0000664000175000017500000000426014660147624016556 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, IndependentPlugin class ArcConf(Plugin, IndependentPlugin): """ The ArcConf plugin is meant for sas adapters, and will collect logs and information for each RAID adapter discovered on the system. """ short_desc = 'arcconf Integrated RAID adapter information' plugin_name = "arcconf" commands = ("arcconf",) def setup(self): # Get the list of available adapters listarcconf = self.collect_cmd_output("arcconf list") # Parse the 'arcconf list' output and extract controller IDs. # For each Controller ID found in 'arcconf list', add commands # for getconfig and GETLOGS # # Sample 'arcconf list' output: # # Controller information # ------------------------------------------------------------- # Controller ID : Status, Slot, Mode, Name, SerialNumber, WWN # ------------------------------------------------------------- # Controller 1: : Optimal, Slot XXXX, XXXX, XXXX, XXXX, XXXX # ------------------------------------------------------------- # Controller 2: : Optimal, Slot XXXX, XXXX, XXXX, XXXX, XXXX if listarcconf['status'] == 0: for line in listarcconf['output'].splitlines(): try: match = re.match(r"^[\s]*Controller (\d)+", line).group(0) controller_id = match.split()[1] if controller_id: # Add new commands with Controller ID self.add_cmd_output([ f"arcconf getconfig {controller_id}", f"arcconf GETLOGS {controller_id} UART", ]) except AttributeError: continue # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/ovn_host.py0000664000175000017500000000606314660147624017005 0ustar arifarif# Copyright (C) 2018 Mark Michelson # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OVNHost(Plugin): short_desc = 'OVN Controller' plugin_name = "ovn_host" profiles = ('network', 'virt', 'openstack_edpm') pidfile = 'ovn-controller.pid' pid_paths = [ '/var/lib/openvswitch/ovn', '/usr/local/var/run/openvswitch', '/run/openvswitch', ] ovs_cmd_pre = "" def setup(self): if os.environ.get('OVS_RUNDIR'): self.pid_paths.append(os.environ.get('OVS_RUNDIR')) if self.get_option("all_logs"): self.add_copy_spec("/var/log/ovn/") else: self.add_copy_spec("/var/log/ovn/*.log") self.add_copy_spec([self.path_join(pp, self.pidfile) for pp in self.pid_paths]) self.add_copy_spec('/etc/sysconfig/ovn-controller') self.add_cmd_output([ f'{self.ovs_cmd_pre}ovs-ofctl -O OpenFlow13 dump-flows br-int', f'{self.ovs_cmd_pre}ovs-vsctl list-br', f'{self.ovs_cmd_pre}ovs-vsctl list Open_vSwitch', ]) self.add_journal(units="ovn-controller") def check_enabled(self): return (any(self.path_isfile(self.path_join(pid_path, self.pidfile)) for pid_path in self.pid_paths) or super().check_enabled()) class RedHatOVNHost(OVNHost, RedHatPlugin): packages = ('openvswitch-ovn-host', 'ovn.*-host', ) var_ansible_gen = "/var/lib/config-data/ansible-generated/ovn-bgp-agent" def setup(self): super().setup() self.add_copy_spec([ self.var_ansible_gen, ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/containers/ovn-bgp-agent/", ]) else: self.add_copy_spec([ "/var/log/containers/ovn-bgp-agent/*.log", ]) class DebianOVNHost(OVNHost, DebianPlugin, UbuntuPlugin): packages = ('ovn-host', ) sunbeam_common_dir = '/var/snap/openstack-hypervisor/common' pid_paths = [ f'{sunbeam_common_dir}/run/ovn', ] def setup(self): if self.is_installed('openstack-hypervisor'): self.ovs_cmd_pre = "openstack-hypervisor." self.add_copy_spec([ f'{self.sunbeam_common_dir}/lib/ovn-metadata-proxy/*.conf', ]) if self.get_option("all_logs"): self.add_copy_spec([ f"{self.sunbeam_common_dir}/var/log/ovn/", ]) else: self.add_copy_spec([ f"{self.sunbeam_common_dir}/var/log/ovn/*.log", ]) super().setup() sos-4.8.0/sos/report/plugins/grafana.py0000664000175000017500000000401514660147624016540 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Pratik Bandarkar # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Grafana(Plugin, IndependentPlugin): short_desc = 'Fetch Grafana configuration, logs and CLI output' plugin_name = "grafana" profiles = ('services', 'openstack', 'openstack_controller') packages = ('grafana',) def setup(self): if self.is_snap: grafana_cli = "grafana.grafana-cli" log_path = "/var/snap/grafana/common/data/log/" config_path = "/var/snap/grafana/current/conf/grafana.ini" self.add_cmd_output("snap info grafana", snap_cmd=True) else: grafana_cli = "grafana-cli" log_path = "/var/log/grafana/" config_path = "/etc/grafana/" self.add_cmd_output([ f'{grafana_cli} plugins ls', f'{grafana_cli} plugins list-remote', f'{grafana_cli} -v', 'grafana-server -v', ], snap_cmd=self.is_snap) log_file_pattern = "*.log*" if self.get_option("all_logs") else "*.log" self.add_copy_spec([ log_path + log_file_pattern, config_path, "/etc/sysconfig/grafana-server", ]) def postproc(self): protect_keys = [ "admin_password", "secret_key", "password", "client_secret", ] inifile = ( "/var/snap/grafana/current/conf/grafana.ini" if self.is_snap else "/etc/grafana/grafana.ini" ) regexp = fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)" self.do_path_regex_sub(inifile, regexp, r"\1*********") sos-4.8.0/sos/report/plugins/zvm.py0000664000175000017500000000540714660147624015763 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin from sos.utilities import is_executable class ZVM(Plugin, IndependentPlugin): plugin_name = 'zvm' short_desc = 'IBM z/VM information' commands = ('vmcp', 'hcp') def setup(self): vm_cmd = None for cmd in self.commands: if is_executable(cmd): vm_cmd = cmd break # vm commands from dbginfo.sh vm_cmds = [ "q userid", "q users", "q privclass", "q cplevel", "q cpservice", "q cpprot user", "q specex", "q ssi", "q cpus", "q srm", "q vtod", "q time full", "q timezone", "q loaddev", "q v osa", "q v dasd", "q v crypto", "q v fcp", "q v pav", "q v sw", "q v st", "q v nic", "q st", "q xstore", "q xstore user system", "q sxspages", "q vmlan", "q vswitch", "q vswitch details", "q vswitch access", "q vswitch active", "q vswitch accesslist", "q vswitch promiscuous", "q vswitch controller", "q port group all active details", "q set", "q comm", "q controller all", "q fcp", "q frames", "q lan", "q lan all details", "q lan all access", "q memassist", "q nic", "q pav", "q proc", "q proc topology", "q mt", "q qioass", "q spaces", "q swch all", "q trace", "q mdcache", "q alloc page", "q alloc spool", "q dump", "q dumpdev", "q pcifunction", "q vmrelocate", "ind load", "ind sp", "ind user" ] vm_id_out = self.collect_cmd_output(f"{vm_cmd} q userid") if vm_id_out['status'] == 0: vm_id = vm_id_out['output'].split()[0] vm_cmds.extend([ f"q reorder {vm_id}", f"q quickdsp {vm_id}", ]) self.add_cmd_output([f"{vm_cmd} {vcmd}" for vcmd in vm_cmds]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/block.py0000664000175000017500000000457414660147624016245 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Block(Plugin, IndependentPlugin): short_desc = 'Block device information' plugin_name = 'block' profiles = ('storage', 'hardware') verify_packages = ('util-linux',) files = ('/sys/block',) def setup(self): self.add_forbidden_path("/sys/block/*/queue/iosched") self.add_file_tags({ '/sys/block/.*/queue/scheduler': 'scheduler' }) self.add_dir_listing('/dev', tags=['ls_dev'], recursive=True) self.add_dir_listing('/sys/block', recursive=True) self.add_cmd_output("blkid -c /dev/null", tags="blkid") self.add_cmd_output("lsblk", tags="lsblk") self.add_cmd_output("lsblk -O -P", tags="lsblk_pairs") self.add_cmd_output([ "lsblk -t", "lsblk -D", "blockdev --report", "losetup -a", ]) # legacy location for non-/run distributions self.add_copy_spec([ "/etc/blkid.tab", "/run/blkid/blkid.tab", "/proc/partitions", "/proc/diskstats", "/sys/block/*/queue/", "/sys/block/sd*/device/timeout", "/sys/block/hd*/device/timeout", "/sys/block/sd*/device/state", "/sys/block/loop*/loop/", ]) cmds = [ "parted -s %(dev)s unit s print", "udevadm info %(dev)s", "udevadm info -a %(dev)s" ] self.add_device_cmd(cmds, devices='block', blacklist='ram.*') self.add_device_cmd("fdisk -l %(dev)s", blacklist="ram.*", devices="block", tags="fdisk_l_sos") lsblk = self.collect_cmd_output("lsblk -f -a -l") # for LUKS devices, collect cryptsetup luksDump if lsblk['status'] == 0: for line in lsblk['output'].splitlines(): if 'crypto_LUKS' in line: dev = line.split()[0] self.add_cmd_output(f'cryptsetup luksDump /dev/{dev}') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sendmail.py0000664000175000017500000000226514660147624016742 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Eugene Teo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Sendmail(Plugin): short_desc = 'sendmail service' plugin_name = "sendmail" profiles = ('services', 'mail') packages = ('sendmail',) def setup(self): self.add_copy_spec("/etc/mail/*") self.add_cmd_output([ 'mailq', 'mailq -Ac' ]) class RedHatSendmail(Sendmail, RedHatPlugin): files = ('/etc/rc.d/init.d/sendmail',) def setup(self): super().setup() self.add_copy_spec('/var/log/maillog') class DebianSendmail(Sendmail, DebianPlugin, UbuntuPlugin): files = ('/etc/init.d/sendmail',) def setup(self): super().setup() self.add_copy_spec("/var/log/mail.*") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sapnw.py0000664000175000017500000001203514660147624016272 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Sapnw(Plugin, RedHatPlugin): short_desc = 'SAP NetWeaver' plugin_name = 'sapnw' profiles = ('sap',) files = ('/usr/sap',) def collect_list_instances(self): """ Collect data on installed instances """ inst_list = self.collect_cmd_output( "/usr/sap/hostctrl/exe/saphostctrl -function ListInstances", suggest_filename="SAPInstances" ) if inst_list['status'] != 0: return sidsunique = set() # Cycle through all the instances, get 'sid', 'instance_number' # and 'vhost' to determine the proper profile for inst_line in inst_list['output'].splitlines(): if ("DAA" not in inst_line and not inst_line.startswith("No instances found")): fields = inst_line.strip().split() if len(fields) < 8: continue sid = fields[3] inst = fields[5] vhost = fields[7] sidsunique.add(sid) path = f"/usr/sap/{sid}/SYS/profile/" if not self.path_exists(path): continue for line in self.listdir(path): if all(f in line for f in [sid, inst, vhost]): ldenv = f'LD_LIBRARY_PATH=/usr/sap/{sid}/SYS/exe/run' # Unicode is assumed here # nuc should be accounted path = f'/usr/sap/{sid}/SYS/exe/uc/linuxx86_64' profile = line.strip() # collect profiles self.add_cmd_output( f"env -i {ldenv} {path}/sappfpar all " f"pf=/usr/sap/{sid}/SYS/profile/{profile}", suggest_filename=f"{profile}_parameters" ) # collect instance status self.add_cmd_output( f"env -i {ldenv} {path}/sapcontrol -nr {inst} " "-function GetProcessList", suggest_filename=f"{sid}_{inst}_GetProcList" ) # collect version info for the various components self.add_cmd_output( f"env -i {ldenv} {path}/sapcontrol -nr {inst} " "-function GetVersionInfo", suggest_filename=f"{sid}_{inst}_GetVersInfo" ) # collect adm user environment lowsid = sid.lower() fname = f"{sid}_{lowsid}adm_{inst}_userenv" self.add_cmd_output( f'su - {lowsid}adm -c "sapcontrol -nr {inst} ' '-function GetEnvironment"', suggest_filename=fname ) # traverse the sids list, collecting info about dbclient for sid in sidsunique: self.add_copy_spec(f"/usr/sap/{sid}/*DVEB*/work/dev_w0") def collect_list_dbs(self): """ Collect data all the installed DBs """ # list installed sap dbs db_list = self.collect_cmd_output( "/usr/sap/hostctrl/exe/saphostctrl -function ListDatabases", suggest_filename="SAPDatabases" ) if db_list['status'] != 0: return for line in db_list['output'].splitlines(): if "Instance name" in line: fields = line.strip().split() dbadm = fields[2][:-1] dbtype = fields[8][:-1] sid = dbadm[3:].upper() if dbtype == 'db6': # IBM DB2 self.add_cmd_output( f"su - {dbadm} -c \"db2 get dbm cfg\"", suggest_filename=f"{sid}_{dbadm}_db2_info" ) elif dbtype == 'sap': # SAP MAXDB sid = fields[2][:-1] self.add_copy_spec( f"/sapdb/{sid}/data/config/{sid}.pah" ) elif dbtype == 'ora': # Oracle sid = fields[2][:-1] self.add_copy_spec(f"/oracle/{sid}/*/dbs/init.ora") elif dbtype == 'syb': # Sybase sid = fields[2][:-1] self.add_copy_spec(f"/sybase/{sid}/ASE*/{sid}.cfg") def setup(self): self.collect_list_instances() self.collect_list_dbs() # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/mellanox_firmware.py0000664000175000017500000001131514660147624020655 0ustar arifarif# Copyright (C) 2023 Nvidia Corporation, Alin Serdean # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import time from sos.report.plugins import Plugin, IndependentPlugin class MellanoxFirmware(Plugin, IndependentPlugin): short_desc = 'Nvidia(Mellanox) firmware tools output' plugin_name = "mellanox_firmware" profiles = ('hardware', 'system') packages = ('mst', 'mstflint') MLNX_STRING = "Mellanox Technologies" def check_enabled(self): """ Checks if this plugin should be executed at all. We will only enable the plugin if there is a Mellanox Technologies network adapter """ lspci = self.exec_cmd("lspci -D -d 15b3::0200") return lspci['status'] == 0 and self.MLNX_STRING in lspci['output'] def collect(self): if not self.get_option('allow_system_changes'): self._log_info("Skipping mst/mlx cable commands as system changes" "would be made. Use --allow-system-changes to" "enable this collection.") return # Run only if mft package is installed. # flint is available from the mft package. cout = self.exec_cmd('flint --version') if cout['status'] != 0: return cout = self.collect_cmd_output('mst start') if cout['status'] != 0: return self.collect_cmd_output('mst cable add') self.collect_cmd_output("mst status -v", timeout=10) self.collect_cmd_output("mlxcables", timeout=10) cout = os.listdir("/dev/mst") mlxcables = [] for device in cout: if 'cable' in device: mlxcables.append(device) for mlxcable in mlxcables: self.collect_cmd_output(f"mlxcables -d {mlxcable} --DDM", timeout=10) self.collect_cmd_output(f"mlxcables -d {mlxcable} --dump", timeout=10) self.collect_cmd_output("mst stop", changes=True) def setup(self): # Get all devices which have the vendor Mellanox Technologies devices = [] device_list = self.collect_cmd_output('lspci -D -d 15b3::0200') # Will return a string of the following format: # 0000:08:00.0 Ethernet controller: Mellanox Technologies MT2892 Family if device_list['status'] != 0: # bail out if there no Mellanox PCI devices return for line in device_list["output"].splitlines(): # Should return 0000:08:00.0 # from the following string # 0000:08:00.0 Ethernet controller: Mellanox Technologies MT2892 # Family devices.append(line[0:8]+'00.0') devices = set(devices) # Mft package is present if OFED is installed # mstflint package is part of the distro and can be installed. commands = [] # mft package is installed if flint command is available cout = self.exec_cmd('flint --version') if cout['status'] != 0: # mstflint package commands # the commands do not support position independent arguments commands = [ ["mstconfig -d ", " -e q"], ["mstflint -d ", " dc"], ["mstflint -d ", " q"], ["mstreg -d ", " --reg_name ROCE_ACCL --get"], ["mstlink -d ", ""], ] else: # mft package commands # the commands do not support position independent arguments commands = [ ["mlxdump -d ", " pcie_uc --all"], ["mstconfig -d ", " -e q"], ["flint -d ", " dc"], ["flint -d ", " q"], ["mlxreg -d ", " --reg_name ROCE_ACCL --get"], ["mlxlink -d ", ""], ["fwtrace -d ", " -i all --tracer_mode FIFO"], ] for device in devices: for command in commands: self.add_cmd_output(f"{command[0]} {device} " f"{command[1]}", timeout=30) # Dump the output of the mstdump command three times # waiting for one second. This output is useful to check # if certain registers changed for _ in range(3): self.add_cmd_output(f"mstdump {device}") time.sleep(1) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/corosync.py0000664000175000017500000000464314660147624017007 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Corosync(Plugin): short_desc = 'Corosync cluster engine' plugin_name = "corosync" profiles = ('cluster',) packages = ('corosync',) def setup(self): self.add_copy_spec([ "/etc/corosync", "/var/lib/corosync/fdata", "/var/log/cluster/corosync.log*" ]) self.add_cmd_output([ "corosync-quorumtool -l", "corosync-quorumtool -s", "corosync-cpgtool", "corosync-cfgtool -s", "corosync-blackbox", "corosync-objctl -a", "corosync-cmapctl -m stats" ]) self.add_cmd_output("corosync-cmapctl", tags="corosync_cmapctl") self.exec_cmd("killall -USR2 corosync") corosync_conf = "/etc/corosync/corosync.conf" if not self.path_exists(corosync_conf): return # collect user-defined logfiles, matching either of pattern: # log_size: filename # or # logging.log_size: filename # (it isnt precise but sufficient) pattern = r'^\s*(logging.)?logfile:\s*(\S+)$' try: cconf = self.path_join("/etc/corosync/corosync.conf") with open(cconf, 'r', encoding='UTF-8') as file: for line in file: if re.match(pattern, line): self.add_copy_spec(re.search(pattern, line).group(2)) except IOError as err: # pylint: disable=broad-except self._log_warn(f"could not read from {corosync_conf}: {err}") def postproc(self): self.do_cmd_output_sub( "corosync-objctl", r"(.*fence.*\.passwd=)(.*)", r"\1******" ) class RedHatCorosync(Corosync, RedHatPlugin): """ Parent class Corosync's setup() will be called """ class DebianCorosync(Corosync, DebianPlugin, UbuntuPlugin): """ Parent class Corosync's setup() will be called """ files = ('/usr/sbin/corosync',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/filesys.py0000664000175000017500000000747314660147624016632 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, CosPlugin, PluginOpt) class Filesys(Plugin, DebianPlugin, UbuntuPlugin, CosPlugin): """Collects general information about the local filesystem(s) and mount points as well as optional information about EXT filesystems. Note that information specific filesystems such as XFS or ZFS is not collected by this plugin, as there are specific plugins for those filesystem types. This plugin will collect /etc/fstab as well as mount information within /proc/, and is responsible for the 'mount' and 'df' symlinks that appear in an sos archive's root. """ short_desc = 'Local file systems' plugin_name = 'filesys' profiles = ('storage',) option_list = [ PluginOpt('lsof', default=False, desc='collect information on all open files'), PluginOpt('dumpe2fs', default=False, desc='dump filesystem info'), PluginOpt('frag', default=False, desc='collect filesystem fragmentation status') ] def setup(self): self.add_copy_spec([ "/proc/fs/", "/proc/mounts", "/proc/filesystems", "/proc/self/mounts", "/proc/self/mountinfo", "/proc/self/mountstats", "/proc/[0-9]*/mountinfo", "/etc/mtab", "/etc/fstab", "/run/mount/utab", ]) self.add_cmd_output("mount -l", root_symlink="mount", tags="mount") self.add_cmd_output("df -al -x autofs", root_symlink="df", tags='df__al') self.add_cmd_output([ "df -aliT -x autofs", "findmnt", "lslocks" ]) self.add_forbidden_path([ # cifs plugin '/proc/fs/cifs', # lustre plugin '/proc/fs/ldiskfs', '/proc/fs/lustre', # nfs plugin '/proc/fs/nfsd', '/proc/fs/nfsfs', # panfs (from Panasas company) provides statistics which can be # very large (100s of GB) '/proc/fs/panfs', # xfs plugin '/proc/fs/xfs' ]) if self.get_option('lsof'): self.add_cmd_output("lsof -b +M -n -l -P", root_symlink="lsof", priority=50) dumpe2fs_opts = '-h' if self.get_option('dumpe2fs'): dumpe2fs_opts = '' mounts = '/proc/mounts' ext_fs_regex = r"^(/dev/\S+).+ext[234]\s+" for dev in self.do_regex_find_all(ext_fs_regex, mounts): self.add_cmd_output(f"dumpe2fs {dumpe2fs_opts} {dev}", tags="dumpe2fs_h") if self.get_option('frag'): self.add_cmd_output(f"e2freefrag {dev}", priority=100) def postproc(self): self.do_file_sub( "/etc/fstab", r"(password=)[^,\s]*", r"\1********" ) # remove expected errors from lsof due to command formatting, but still # keep stderr so other errors are reported regex = (r"(lsof: (avoiding (.*?)|WARNING: can't stat\(\) (.*?))|" "Output information may be incomplete.)\n") self.do_cmd_output_sub("lsof", regex, '') class RedHatFilesys(Filesys, RedHatPlugin): def setup(self): super().setup() self.add_cmd_output('ls -ldZ /tmp') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/lvm2.py0000664000175000017500000001114514660147624016023 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, IndependentPlugin, SoSPredicate, PluginOpt) class Lvm2(Plugin, IndependentPlugin): short_desc = 'Logical Volume Manager 2' plugin_name = 'lvm2' profiles = ('storage',) option_list = [ PluginOpt('lvmdump', default=False, desc='collect an lvmdump tarball'), PluginOpt('lvmdump-am', default=False, desc=('attempt to collect lvmdump with advanced options and ' 'raw metadata')), PluginOpt('metadata', default=False, desc='attempt to collect headers and metadata via pvck') ] def do_lvmdump(self, metadata=False): """Collects an lvmdump in standard format with optional metadata archives for each physical volume present. """ lvmdump_path = self.get_cmd_output_path(name="lvmdump", make=False) lvmdump_cmd = "lvmdump %s -d '%s'" lvmdump_opts = "" if metadata: lvmdump_opts = "-a -m" cmd = lvmdump_cmd % (lvmdump_opts, lvmdump_path) self.add_cmd_output(cmd, chroot=self.tmp_in_sysroot()) def get_pvck_output(self): """ Collects the output of the command pvck for each block device present in the system. """ block_list = self.exec_cmd( 'pvs -o pv_name --no-headings' ) if block_list['status'] == 0: for line in block_list['output'].splitlines(): cmds = [ f"pvck --dump headers {line}", f"pvck --dump metadata {line}", f"pvck --dump metadata_all {line} -v", f"pvck --dump metadata_search {line} -v" ] self.add_cmd_output(cmds, subdir="metadata") def setup(self): # When running LVM2 comamnds: # - use nolocking if supported, else locking_type 0 (no locks) # from lvm.conf: Turn locking off by setting to 0 (dangerous: # risks metadata corruption if LVM2 commands get run # concurrently). This avoids the possibility of hanging lvm # commands when another process or node holds a conflicting # lock. # - use metadata_read_only 1 (forbid on-disk changes). Although # all LVM2 commands we use should be read-only, any LVM2 # command may attempt to recover on-disk data in some cases. # This option prevents such changes, allowing safe use of # locking_type=0. # - use --foreign option in pvs, lvs, vgs and vgdisplay commands # to support HA-LVM deployments nolock = {'cmd': 'vgdisplay -h', 'output': '--nolocking'} if bool(SoSPredicate(self, cmd_outputs=nolock)): lvm_opts = '--config="global{metadata_read_only=1}" --nolocking' else: lvm_opts = '--config="global{locking_type=0 metadata_read_only=1}"' lvm_opts_foreign = lvm_opts + ' --foreign' self.add_cmd_output( f"vgdisplay -vv {lvm_opts_foreign}", root_symlink="vgdisplay", tags="vgdisplay" ) pvs_cols = 'pv_mda_free,pv_mda_size,pv_mda_count,pv_mda_used_count' pvs_cols = pvs_cols + ',' + 'pe_start' vgs_cols = 'vg_mda_count,vg_mda_free,vg_mda_size,vg_mda_used_count' vgs_cols = vgs_cols + ',' + 'vg_tags,systemid,lock_type' lvs_cols = ('lv_tags,devices,lv_kernel_read_ahead,lv_read_ahead,' 'stripes,stripesize') self.add_cmd_output(f"lvs -a -o +{lvs_cols} {lvm_opts_foreign}", tags="lvs_headings") self.add_cmd_output( f"pvs -a -v -o +{pvs_cols} {lvm_opts_foreign}", tags="pvs_headings") self.add_cmd_output(f"vgs -v -o +{vgs_cols} {lvm_opts_foreign}", tags="vgs_headings") self.add_cmd_output([ f"pvscan -v {lvm_opts}", f"vgscan -vvv {lvm_opts}", ]) self.add_copy_spec("/etc/lvm") self.add_copy_spec("/run/lvm") if self.get_option('lvmdump'): self.do_lvmdump() elif self.get_option('lvmdump-am'): self.do_lvmdump(metadata=True) if self.get_option('metadata'): self.get_pvck_output() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/cs.py0000664000175000017500000001100414660147624015542 0ustar arifarif# Copyright (C) 2007-2010 Red Hat, Inc., Kent Lamb # Marc Sauton # Pierre Carrier # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, RedHatPlugin class CertificateSystem(Plugin, RedHatPlugin): short_desc = 'Certificate System and Dogtag' plugin_name = 'cs' profiles = ('identity', 'security') packages = ( "redhat-cs", "rhpki-common", "pki-common", "redhat-pki", "dogtag-pki", "pki-base" ) files = ( "/opt/redhat-cs", "/usr/share/java/rhpki", "/usr/share/java/pki" ) def checkversion(self): """ Get Certificate System version """ if (self.is_installed("redhat-cs") or self.path_exists("/opt/redhat-cs")): return 71 if self.is_installed("rhpki-common") or glob("/var/lib/rhpki-*"): return 73 # 8 should cover dogtag if self.is_installed("pki-common"): return 8 if self.is_installed("redhat-pki") or \ self.is_installed("dogtag-pki") or \ self.is_installed("pki-base"): return 9 return False def setup(self): csversion = self.checkversion() if not csversion: self.add_alert("Red Hat Certificate System not found.") return if csversion == 71: self.add_copy_spec([ "/opt/redhat-cs/slapd-*/logs/access", "/opt/redhat-cs/slapd-*/logs/errors", "/opt/redhat-cs/slapd-*/config/dse.ldif", "/opt/redhat-cs/cert-*/errors", "/opt/redhat-cs/cert-*/config/CS.cfg", "/opt/redhat-cs/cert-*/access", "/opt/redhat-cs/cert-*/errors", "/opt/redhat-cs/cert-*/system", "/opt/redhat-cs/cert-*/transactions", "/opt/redhat-cs/cert-*/debug", "/opt/redhat-cs/cert-*/tps-debug.log" ]) if csversion == 73: self.add_copy_spec([ "/var/lib/rhpki-*/conf/*cfg*", "/var/lib/rhpki-*/conf/*.ldif", "/var/lib/rhpki-*/logs/debug", "/var/lib/rhpki-*/logs/catalina.*", "/var/lib/rhpki-*/logs/ra-debug.log", "/var/lib/rhpki-*/logs/transactions", "/var/lib/rhpki-*/logs/system" ]) if csversion in (73, 8): self.add_copy_spec([ "/etc/dirsrv/slapd-*/dse.ldif", "/var/log/dirsrv/slapd-*/access", "/var/log/dirsrv/slapd-*/errors" ]) self.add_file_tags({ "/var/log/dirsrv/*/access": "dirsrv_access" }) if csversion == 8: self.add_copy_spec([ "/etc/pki-*/CS.cfg", "/var/lib/pki-*/conf/*cfg*", "/var/log/pki-*/debug", "/var/log/pki-*/catalina.*", "/var/log/pki-*/ra-debug.log", "/var/log/pki-*/transactions", "/var/log/pki-*/system" ]) if csversion == 9: # Get logs and configs for each subsystem if installed for subsystem in ('ca', 'kra', 'ocsp', 'tks', 'tps'): self.add_copy_spec([ "/var/lib/pki/*/" + subsystem + "/conf/CS.cfg", "/var/lib/pki/*/logs/" + subsystem + "/system", "/var/lib/pki/*/logs/" + subsystem + "/transactions", "/var/lib/pki/*/logs/" + subsystem + "/debug", "/var/lib/pki/*/logs/" + subsystem + "/selftests.log" ]) # Common log files self.add_copy_spec([ "/var/lib/pki/*/logs/catalina.*", "/var/lib/pki/*/logs/localhost*.log", "/var/lib/pki/*/logs/localhost*.txt", "/var/lib/pki/*/logs/manager*.log", "/var/lib/pki/*/logs/host-manager*.log", "/var/lib/pki/*/logs/tps/tokendb-audit.log" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovirt_engine_backup.py0000664000175000017500000000333714660147624021164 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Miguel Martin # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from datetime import datetime from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class OVirtEngineBackup(Plugin, RedHatPlugin): short_desc = 'oVirt Engine database backup' packages = ("ovirt-engine-tools-backup",) plugin_name = "ovirt_engine_backup" option_list = [ PluginOpt('backupdir', default='/var/lib/ovirt-engine-backup', desc='Directory where backups are generated'), PluginOpt('tmpdir', default='/tmp', desc='temp dir to use for engine-backup') ] profiles = ("virt",) def setup(self): now = datetime.now().strftime("%Y%m%d%H%M%S") backup_filename = self.path_join( self.get_option("backupdir"), f"engine-db-backup-{now}.tar.gz" ) log_filename = self.path_join( self.get_option("backupdir"), f"engine-db-backup-{now}.log" ) cmd = ("engine-backup --mode=backup --scope=db" f" --file={backup_filename} --log={log_filename}" f" --tmpdir={self.get_option('tmpdir')}") res = self.collect_cmd_output(cmd, suggest_filename="engine-backup") if res['status'] == 0: self.add_copy_spec([ backup_filename, log_filename ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_iscsi.py0000664000175000017500000000305714660147624017257 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephISCSI(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = "CEPH iSCSI" plugin_name = "ceph_iscsi" profiles = ("storage", "virt", "container", "ceph") packages = ("ceph-iscsi",) services = ("rbd-target-api", "rbd-target-gw") containers = ("rbd-target-api.*", "rbd-target-gw.*") def setup(self): all_logs = self.get_option("all_logs") self.add_copy_spec(["/etc/tcmu/tcmu.conf",]) if not all_logs: self.add_copy_spec([ "/var/log/**/ceph-client.*.log", "/var/log/**/rbd-target-api.log", "/var/log/**/rbd-target-gw.log", "/var/log/**/tcmu-runner.log", "/var/log/tcmu-runner.log" ]) else: self.add_copy_spec([ "/var/log/**/ceph-client.*.log*", "/var/log/**/rbd-target-api.log*", "/var/log/**/rbd-target-gw.log*", "/var/log/**/tcmu-runner.log*", "/var/log/tcmu-runner.log*" ]) self.add_cmd_output([ "gwcli info", "gwcli ls" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/vault.py0000664000175000017500000000337214660147624016301 0ustar arifarif# Copyright (C) 2023 Canonical Ltd., Arif Ali # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class Vault(Plugin, UbuntuPlugin): """The Vault plugin collects the current status of the vault snap on a Ubuntu machine. It will collect logs from journal, vault status and configuration """ short_desc = 'Manage access to secrets and protect sensitive data' plugin_name = 'vault' profiles = ('sysmgmt', 'security') services = ('vault',) package = ('vault',) def setup(self): vault_cfg = "/var/snap/vault/common/vault.hcl" self.add_copy_spec(vault_cfg) try: with open(vault_cfg, 'r', encoding='UTF-8') as cfile: for line in cfile.read().splitlines(): if not line: continue words = line.split('=') if words[0].strip() == 'api_addr': api_addr = words[1].strip('\" ') self.add_cmd_output("vault status", env={'VAULT_ADDR': api_addr}) except IOError as error: self._log_error(f'Could not open conf file {vault_cfg}: {error}') def postproc(self): self.do_file_sub( "/var/snap/vault/common/vault.hcl", r"(password\s?=\s?).*", r"\1******" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ipvs.py0000664000175000017500000000205614660147624016125 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, SoSPredicate class Ipvs(Plugin, RedHatPlugin, DebianPlugin): short_desc = 'Linux IP virtual server' plugin_name = 'ipvs' profiles = ('cluster', 'network') packages = ('ipvsadm',) def setup(self): self.add_cmd_output([ "ipvsadm -Ln", "ipvsadm -Ln --connection", "ipvsadm -Ln --persistent-conn", "ipvsadm -Ln --rate", "ipvsadm -Ln --stats", "ipvsadm -Ln --thresholds", "ipvsadm -Ln --timeout" ], pred=SoSPredicate(self, kmods=['ip_vs'])) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/login.py0000664000175000017500000000200114660147624016242 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Login(Plugin, IndependentPlugin): short_desc = 'login information' plugin_name = 'login' profiles = ('system', 'identity') def setup(self): self.add_cmd_output("last", root_symlink="last") self.add_cmd_output([ "last reboot", "last shutdown", "lastlog", "lastlog -u 0-999", "lastlog -u 1000-60000", "lastlog -u 60001-65536", "lastlog -u 65537-4294967295" ]) self.add_copy_spec([ "/etc/login.defs", "/etc/default/useradd", ]) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/openshift_ovn.py0000664000175000017500000000515414660147624020027 0ustar arifarif# Copyright (C) 2021 Nadia Pinaeva # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OpenshiftOVN(Plugin, RedHatPlugin): """This plugin is used to collect OCP 4.x OVN logs. """ short_desc = 'Openshift OVN' plugin_name = "openshift_ovn" containers = ('ovnkube-master', 'ovnkube-node', 'ovn-ipsec', 'ovnkube-controller') profiles = ('openshift',) def setup(self): all_logs = self.get_option("all_logs") self.add_copy_spec([ "/var/lib/ovn/etc/ovnnb_db.db", "/var/lib/ovn/etc/ovnsb_db.db", "/var/lib/openvswitch/etc/keys" ], sizelimit=300) # Collect ovn interconnect specific db files if exists. self.add_copy_spec([ "/var/lib/ovn-ic/etc/ovnnb_db.db", "/var/lib/ovn-ic/etc/ovnsb_db.db" ], sizelimit=300) # Collect libovsdb logs in case of ovn interconnect setup. if not all_logs: self.add_copy_spec([ "/var/lib/ovn-ic/etc/libovsdb.log", "/var/lib/ovn-ic/etc/libovsdb*log.gz" ], sizelimit=100) else: self.add_copy_spec("/var/lib/ovn-ic/etc/libovsdb*log*") # The ovn cluster/status is not valid anymore for interconnect setup. self.add_cmd_output([ 'ovn-appctl -t /var/run/ovn/ovnnb_db.ctl ' + 'cluster/status OVN_Northbound', 'ovn-appctl -t /var/run/ovn/ovnsb_db.ctl ' + 'cluster/status OVN_Southbound'], container='ovnkube-master') self.add_cmd_output([ 'ovs-appctl -t /var/run/ovn/ovn-controller.*.ctl ' + 'ct-zone-list'], container='ovnkube-node') self.add_cmd_output([ 'ovs-appctl -t /var/run/ovn/ovn-controller.*.ctl ' + 'ct-zone-list'], container='ovnkube-controller') # Collect ovs ct-zone-list directly on host for interconnect setup. self.add_cmd_output([ 'ovs-appctl -t /var/run/ovn-ic/ovn-controller.*.ctl ' + 'ct-zone-list']) self.add_cmd_output([ 'ovs-appctl -t ovs-monitor-ipsec tunnels/show', 'ipsec status', 'certutil -L -d sql:/etc/ipsec.d'], container='ovn-ipsec') sos-4.8.0/sos/report/plugins/smartcard.py0000664000175000017500000000277014660147624017127 0ustar arifarif# Copyright (C) 2007 Sadique Puthen # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Smartcard(Plugin, RedHatPlugin): short_desc = 'PKCS#11 smart cards' plugin_name = 'smartcard' profiles = ('security', 'identity', 'hardware') files = ('/etc/pam_pkcs11/pam_pkcs11.conf',) # The pam_pkcs11 is available only in RHEL7 packages = ('pam_pkcs11', 'pcsc-tools', 'opensc', 'pcsc-lite', 'pcsc-lite-ccid') def setup(self): self.add_copy_spec([ "/etc/reader.conf", "/etc/reader.conf.d/", "/etc/pam_pkcs11/", "/etc/opensc-*.conf", "/etc/pkcs11/modules/*.module", "/usr/share/p11-kit/modules/*.module" ]) self.add_cmd_output([ "pklogin_finder debug", "pcsc_scan", "pkcs11-tool --show-info", "pkcs11-tool --list-mechanisms", "pkcs11-tool --list-slots", "pkcs11-tool --list-objects" ]) self.add_dir_listing('/usr/lib*/pam_pkcs11/') self.add_forbidden_path("/etc/pam_pkcs11/nssdb/key[3-4].db") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/pulp.py0000664000175000017500000001724614660147624016133 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from re import match from shlex import quote from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Pulp(Plugin, RedHatPlugin): short_desc = 'Pulp platform' plugin_name = "pulp" packages = ("pulp-server", "pulp-katello", "python3-pulpcore") files = ("/etc/pulp/settings.py",) option_list = [ PluginOpt('tasks', default=200, desc='number of tasks to collect from DB queries') ] dbhost = "localhost" dbport = "27017" dbuser = "" dbpassword = "" def setup(self): # get mongo DB host and port from line like: # seeds: host1:27017,host2:27017 # take just the very first URI and ignore possible failover # if no such config is present, default to localhost:27017 # further, take optional user credentials - here we assume the # credentials dont contain a whitespace character (that would # make the parsing more difficult) # # further, collect location of CA file for contacting qpid in section # [messaging] # certfile: /etc/pki/katello/qpid_client_striped.crt messaging_cert_file = "" in_messaging_section = False try: with open("/etc/pulp/server.conf", 'r', encoding='UTF-8') as file: pulp_lines = file.read().splitlines() for line in pulp_lines: if match(r"^\s*seeds:\s+\S+:\S+", line): uri = line.split()[1].split(',')[0].split(':') self.dbhost = uri[0] self.dbport = uri[1] if match(r"\s*username:\s+\S+", line): self.dbuser = f"-u {line.split()[1]}" if match(r"\s*password:\s+\S+", line): self.dbpassword = f"-p {line.split()[1]}" if line.startswith("[messaging]"): in_messaging_section = True if in_messaging_section and line.startswith("certfile:"): messaging_cert_file = line.split()[1] in_messaging_section = False except IOError: # fallback when the cfg file is not accessible pass self.add_file_tags({ '/etc/default/pulp_workers': 'pulp_worker_defaults' }) self.add_copy_spec([ "/etc/pulp/*.conf", "/etc/pulp/settings.py", "/etc/pulp/settings.local.py", "/etc/pulp/server/plugins.conf.d/", "/etc/default/pulp*", "/var/log/httpd/pulp-http.log*", "/var/log/httpd/pulp-https.log*", "/var/log/httpd/pulp-http_access_ssl.log*", "/var/log/httpd/pulp-https_access_ssl.log*", "/var/log/httpd/pulp-http_error_ssl.log*", "/var/log/httpd/pulp-https_error_ssl.log*" ]) num_tasks = self.get_option('tasks') mtasks = self.build_mongo_cmd( f'\"DBQuery.shellBatchSize={num_tasks};; ' 'db.task_status.find().sort({finish_time: -1})' '.pretty().shellPrint()\"' ) mres = self.build_mongo_cmd( f'\"DBQuery.shellBatchSize={num_tasks};; ' 'db.reserved_resources.find().pretty().shellPrint()\"' ) prun = self.build_mongo_cmd( fr'"DBQuery.shellBatchSize={num_tasks};; ' r'db.task_status.find({state:{\$ne: \"finished\"}}).pretty()' r'.shellPrint()"' ) # prints mongo collection sizes sorted from biggest and in human # readable output csizes = self.build_mongo_cmd( '\"function humanReadable(bytes) {' ' var i = -1;' ' var byteUnits = [\'kB\', \'MB\', \'GB\', \'TB\', \'PB\', ' ' \'EB\', \'ZB\', \'YB\'];' ' do {' ' bytes = bytes / 1024;' ' i++;' ' } while (bytes > 1024);' ' return Math.max(bytes, 0.1).toFixed(1) + \' \' + byteUnits[i];' '};' 'var collectionNames = db.getCollectionNames(), stats = [];' 'collectionNames.forEach(function (n) {' ' stats.push(db[n].stats());' ' });' 'stats = stats.sort(function(a, b) {' ' return b[\'size\'] - a[\'size\']; });' 'for (var c in stats) {' ' print(stats[c][\'ns\'] + \': \' +' ' humanReadable(stats[c][\'size\']) + \' (\' +' ' humanReadable(stats[c][\'storageSize\']) + \')\'); }\"' ) dbstats = self.build_mongo_cmd('\"db.stats()\"') self.add_cmd_output(mtasks, suggest_filename="mongo-task_status") self.add_cmd_output(mres, suggest_filename="mongo-reserved_resources") self.add_cmd_output(prun, suggest_filename="pulp-running_tasks") self.add_cmd_output(csizes, suggest_filename="mongo-collection_sizes") self.add_cmd_output(dbstats, suggest_filename="mongo-db_stats") for opt in "quc": self.add_cmd_output( f"qpid-stat -{opt} --ssl-certificate=" f"{messaging_cert_file} -b amqps://localhost:5671", tags=f"qpid_stat_{opt}") self.add_cmd_output( "sudo -u pulp PULP_SETTINGS='/etc/pulp/settings.py' " "DJANGO_SETTINGS_MODULE='pulpcore.app.settings' dynaconf list", suggest_filename="dynaconf_list" ) def build_mongo_cmd(self, query): """ Build mongoDB command """ _cmd = "bash -c %s" _mondb = (f"--host {self.dbhost} --port {self.dbport} {self.dbuser} " f"{self.dbpassword}") _moncmd = "mongo pulp_database %s --eval %s" return _cmd % quote(_moncmd % (_mondb, query)) def postproc(self): # Handle all ".conf" files under /etc/pulp - note that this includes # files nested at several distinct directory levels. For this reason we # use a regex that matches all these path components with ".*", and # ensure that the path ends with ".conf". etcreg = r"(([a-z].*(passw|token|cred|secret).*)\:(\s))(.*)" repl = r"\1 ********" self.do_path_regex_sub(r"/etc/pulp/(.*)\.conf$", etcreg, repl) # Now handle JSON-formatted data in the same /etc/pulp directory # structure. We use a different substitution string here to preserve # the file's JSON syntax. jreg = r"(\s*\".*(passw|cred|token|secret).*\"\s*:(\s))(.*)(\w+)" repl = r"\1********" self.do_path_regex_sub("/etc/pulp(.*)(.json$)", jreg, repl) # obfuscate SECRET_KEY = .., 'PASSWORD': .., # and AUTH_LDAP_BIND_PASSWORD = .. # in dynaconf list output and also in settings.py # count with option that PASSWORD is with(out) quotes or in capitals key_pass_re = r"((?:SECRET_KEY|AUTH_LDAP_BIND_PASSWORD)" \ r"(?:\<.+\>)?(\s*=)?|(password|PASSWORD)" \ r"(\"|'|:)+)\s*(\S*)" repl = r"\1 ********" self.do_path_regex_sub("/etc/pulp/settings.py", key_pass_re, repl) self.do_path_regex_sub("/etc/pulp/settings.local.py", key_pass_re, repl) self.do_cmd_output_sub("dynaconf list", key_pass_re, repl) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/etcd.py0000664000175000017500000000422014660147624016056 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc. Neependra Khare # Copyright (C) 2015 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Etcd(Plugin, RedHatPlugin): """The etcd plugin collects information from the etcd key-value store. It is primarily used by Kubernetes/OpenShift clusters and is often run inside a container within the cluster. Collections will default to executing within an `etcdctl` container, if one is present, and only execute on the host if such a container is not currently running. The `etcdctl` name preference is adopted from OpenShift Container Platform deployments. This plugin is written for etcd v3 and later. """ short_desc = 'etcd plugin' plugin_name = 'etcd' packages = ('etcd',) profiles = ('container', 'system', 'services', 'cluster') files = ('/etc/etcd/etcd.conf',) containers = ('etcdctl', 'etcd') services = ('etcd',) def setup(self): etcd_con = None for con in self.containers: if self.get_container_by_name(con): etcd_con = con break self.add_file_tags({ '/etc/etcd/etcd.conf': 'etcd_conf' }) self.add_forbidden_path([ '/etc/etcd/ca', '/etc/etcd/*.key' ]) self.add_dir_listing('/var/lib/etcd/', container=etcd_con, recursive=True) self.add_copy_spec('/etc/etcd', container=etcd_con) subcmds = [ 'version', 'member list', 'alarm list', 'endpoint status', 'endpoint health' ] self.add_cmd_output( [f"etcdctl {sub}" for sub in subcmds], container=etcd_con ) # vim: et ts=5 sw=4 sos-4.8.0/sos/report/plugins/abrt.py0000664000175000017500000000301414660147624016067 0ustar arifarif# Copyright (C) 2010 Red Hat, Inc., Tomas Smetana # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Abrt(Plugin, RedHatPlugin): short_desc = 'Automatic Bug Reporting Tool' plugin_name = "abrt" profiles = ('system', 'debug') packages = ('abrt-cli', 'abrt-gui', 'abrt') files = ('/var/spool/abrt',) option_list = [ PluginOpt("detailed", default=False, desc="collect detailed information for every report") ] def setup(self): self.add_cmd_output("abrt-cli status", tags=["abrt_status", "abrt_status_bare"]) abrt_list = self.collect_cmd_output("abrt-cli list") if self.get_option("detailed") and abrt_list['status'] == 0: for line in abrt_list["output"].splitlines(): if line.startswith("Directory"): self.add_cmd_output("abrt-cli info -d " f"'{line.split()[1]}'") self.add_copy_spec([ "/etc/abrt/abrt.conf", "/etc/abrt/abrt-action-save-package-data.conf", "/etc/abrt/plugins" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ppp.py0000664000175000017500000000152014660147624015736 0ustar arifarif# Copyright (C) 2007 Sadique Puthen # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Ppp(Plugin, IndependentPlugin): short_desc = 'Point-to-point protocol' plugin_name = 'ppp' profiles = ('system', 'network') packages = ('ppp',) def setup(self): self.add_copy_spec([ "/etc/wvdial.conf", "/etc/ppp", "/var/log/ppp" ]) self.add_cmd_output("adsl-status") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_nova.py0000664000175000017500000002140014660147624020150 0ustar arifarif# Copyright (C) 2009 Red Hat, Inc., Joey Boggs # Copyright (C) 2012 Rackspace US, Inc., # Justin Shepherd # Copyright (C) 2013 Red Hat, Inc., Jeremy Agee # Copyright (C) 2015 Red Hat, Inc., Abhijeet Kasurde # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import re from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class OpenStackNova(Plugin): short_desc = 'OpenStack Nova' plugin_name = "openstack_nova" profiles = ('openstack', 'openstack_controller', 'openstack_compute', 'openstack_edpm') containers = ('.*nova_api',) var_puppet_gen = "/var/lib/config-data/puppet-generated/nova" service_name = "openstack-nova-api.service" apachepkg = None def setup(self): # collect commands output only if the openstack-nova-api service # is running in_container = self.container_exists('.*nova_api') if self.is_service_running(self.service_name) or in_container: nova_config = "" # if containerized we need to pass the config to the cont. if in_container: nova_config = "--config-dir " + self.var_puppet_gen + \ "/etc/nova/" self.add_cmd_output( "nova-manage " + nova_config + " db version", suggest_filename="nova-manage_db_version" ) self.add_cmd_output( "nova-manage " + nova_config + " fixed list", suggest_filename="nova-manage_fixed_list" ) self.add_cmd_output( "nova-manage " + nova_config + " floating list", suggest_filename="nova-manage_floating_list" ) self.add_cmd_output( "nova-status " + nova_config + " upgrade check", suggest_filename="nova-status_upgrade_check" ) vars_all = [p in os.environ for p in [ 'OS_USERNAME', 'OS_PASSWORD']] vars_any = [p in os.environ for p in [ 'OS_TENANT_NAME', 'OS_PROJECT_NAME']] if not (all(vars_all) and any(vars_any)): self.soslog.warning("Not all environment variables set. " "Source the environment file for the user " "intended to connect to the OpenStack " "environment.") else: self.add_cmd_output("openstack compute service list") self.add_cmd_output("openstack flavor list --long") self.add_cmd_output("openstack compute agent list") self.add_cmd_output("nova version-list") self.add_cmd_output("openstack hypervisor list") self.add_cmd_output("openstack quota show") self.add_cmd_output("openstack hypervisor stats show") res = self.collect_cmd_output( "openstack server list --all-projects" ) # get details for each nova instance if res['status'] == 0: nova_instances = res['output'] for instance in nova_instances.splitlines()[3:-1]: instance = instance.split()[1] cmd = f"openstack server show {instance}" self.add_cmd_output(cmd) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/nova/", f"/var/log/{self.apachepkg}*/nova*", "/var/lib/nova/instances/*/console.log", ]) else: novadir = '/var/log/nova/' novalogs = [ "nova-api.log*", "nova-compute.log*", "nova-conductor.log*", "nova-metadata-api.log*", "nova-manage.log*", "nova-placement-api.log*", "nova-scheduler.log*" ] for novalog in novalogs: self.add_copy_spec(self.path_join(novadir, novalog)) self.add_copy_spec([ f"/var/log/{self.apachepkg}*/nova*.log", f"/var/log/{self.apachepkg}*/placement*.log", ]) npaths = ['', '_libvirt', '_metadata', '_placement'] syspaths = [ '/etc/nova/', '/etc/my.cnf.d/tripleo.cnf', '/etc/httpd/conf/', '/etc/httpd/conf.d/', '/etc/httpd/conf.modules.d/*.conf' ] # excludes httpd'ish specs in the libvirt path specs = [ "/etc/nova/", "authorized_keys", self.var_puppet_gen + "/../memcached/etc/sysconfig/memcached", self.var_puppet_gen + "/var/spool/cron/nova", self.var_puppet_gen + "_libvirt/etc/libvirt/", self.var_puppet_gen + "_libvirt/etc/nova/migration/", self.var_puppet_gen + "_libvirt/var/lib/nova/.ssh/config" ] + list( filter(re.compile('^((?!libvirt.+httpd).)*$').match, [f'{self.var_puppet_gen}{p}{s}' for p in npaths for s in syspaths ])) self.add_copy_spec(specs) def apply_regex_sub(self, regexp, subst): """ Apply regex substitution """ self.do_path_regex_sub("/etc/nova/*", regexp, subst) for npath in ['', '_libvirt', '_metadata', '_placement']: self.do_path_regex_sub( f"{self.var_puppet_gen}{npath}/etc/nova/*", regexp, subst) def postproc(self): protect_keys = [ "ldap_dns_password", "neutron_admin_password", "rabbit_password", "qpid_password", "powervm_mgr_passwd", "virtual_power_host_pass", "xenapi_connection_password", "password", "host_password", "vnc_password", "admin_password", "connection_password", "memcache_secret_key", "s3_secret_key", "metadata_proxy_shared_secret", "fixed_key", "transport_url", "rbd_secret_uuid" ] connection_keys = ["connection", "sql_connection"] join_con_keys = "|".join(connection_keys) self.apply_regex_sub( fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.apply_regex_sub( fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) class DebianNova(OpenStackNova, DebianPlugin, UbuntuPlugin): apachepkg = "apache2" nova = False packages = ( 'nova-api-ec2', 'nova-api-metadata', 'nova-api-os-compute', 'nova-api-os-volume', 'nova-common', 'nova-compute', 'nova-compute-kvm', 'nova-compute-lxc', 'nova-compute-qemu', 'nova-compute-uml', 'nova-compute-xcp', 'nova-compute-xen', 'nova-xcp-plugins', 'nova-consoleauth', 'nova-network', 'nova-scheduler', 'nova-volume', 'novnc', 'python-nova', 'python-novnc', 'python3-nova', ) service_name = "nova-api.service" def setup(self): super().setup() self.add_copy_spec([ "/etc/sudoers.d/nova_sudoers", "/usr/share/polkit-1/rules.d/60-libvirt.rules", ]) class RedHatNova(OpenStackNova, RedHatPlugin): apachepkg = "httpd" nova = False packages = ('openstack-selinux',) def setup(self): super().setup() self.add_copy_spec([ "/etc/logrotate.d/openstack-nova", "/etc/polkit-1/localauthority/50-local.d/50-nova.pkla", "/etc/sudoers.d/nova", "/etc/security/limits.d/91-nova.conf", "/etc/sysconfig/openstack-nova-novncproxy", "/var/lib/openstack/config/nova", "/var/lib/openstack/containers/nova*.json" ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/httpd/placement*", "/var/log/containers/nova/*" ]) else: self.add_copy_spec([ "/var/log/httpd/placement*.log", "/var/log/containers/nova/*.log" ]) self.add_forbidden_path([ "/var/lib/openstack/config/nova/ssh-privatekey" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/bcache.py0000664000175000017500000000432114660147624016346 0ustar arifarif# Copyright (C) 2021, Canonical ltd # Ponnuvel Palaniyappan # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate class Bcache(Plugin, IndependentPlugin): short_desc = 'Bcache statistics' plugin_name = 'bcache' profiles = ('storage', 'hardware') files = ('/sys/fs/bcache',) def setup(self): # Caution: reading /sys/fs/bcache/*/cache0/priority_stats is known # to degrade performance on old kernels. Needs care if that's ever # considered for inclusion here. # see: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1840043 self.add_forbidden_path([ '/sys/fs/bcache/*/*/priority_stats', ]) self.add_copy_spec([ '/sys/block/bcache*/bcache/cache/internal/copy_gc_enabled', '/sys/block/bcache*/bcache/cache_mode', '/sys/block/bcache*/bcache/dirty_data', '/sys/block/bcache*/bcache/io_errors', '/sys/block/bcache*/bcache/sequential_cutoff', '/sys/block/bcache*/bcache/stats_hour/bypassed', '/sys/block/bcache*/bcache/stats_hour/cache_hit_ratio', '/sys/block/bcache*/bcache/stats_hour/cache_hits', '/sys/block/bcache*/bcache/stats_hour/cache_misses', '/sys/block/bcache*/bcache/writeback_percent', '/sys/fs/bcache/*/average_key_size', '/sys/fs/bcache/*/bdev*/*', '/sys/fs/bcache/*/bdev*/stat_*/*', '/sys/fs/bcache/*/block_size', '/sys/fs/bcache/*/bucket_size', '/sys/fs/bcache/*/cache_available_percent', '/sys/fs/bcache/*/congested_*_threshold_us', '/sys/fs/bcache/*/internal/*', '/sys/fs/bcache/*/stats_*/*', '/sys/fs/bcache/*/tree_depth', ], pred=SoSPredicate(self, kmods=['bcache'])) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/process.py0000664000175000017500000001035114660147624016617 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import re from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Process(Plugin, IndependentPlugin): short_desc = 'process information' plugin_name = 'process' profiles = ('system',) option_list = [ PluginOpt('lsof', default=True, desc='collect info on all open files'), PluginOpt('lsof-threads', default=False, desc='collect threads\' open file info if supported'), PluginOpt('smaps', default=False, desc='collect /proc/*/smaps files'), PluginOpt('samples', default=20, val_type=int, desc='number of iotop samples to collect'), PluginOpt('numprocs', default=2048, val_type=int, desc='number of process to collect /proc data of') ] def setup(self): ps_axo = "ps axo" # process group and thread options ps_group_opts = "pid,ppid,user,group,lwp,nlwp,start_time,comm,cgroup" ps_sched_opts = "flags,state,uid,pid,ppid,pgid,sid,cls,pri,psr,addr," ps_sched_opts += "sz,wchan:20,lstart,tty,time,cmd" self.add_copy_spec([ "/proc/sched_debug", "/proc/stat", "/sys/kernel/debug/sched/debug", "/sys/kernel/debug/sched/features" ]) procs = [p for p in self.listdir("/proc") if re.match("[0-9]", p)] if self.get_option("numprocs"): procs = procs[:self.get_option("numprocs")] for proc in procs: self.add_copy_spec([ f"/proc/{proc}/status", f"/proc/{proc}/cpuset", f"/proc/{proc}/oom_*", f"/proc/{proc}/stack", f"/proc/{proc}/limits", ]) if self.get_option("smaps"): self.add_copy_spec("/proc/[0-9]*/smaps") self.add_cmd_output("ps auxwwwm", root_symlink="ps", tags=['ps_aux', 'ps_auxww', 'ps_auxwww', 'ps_auxwwwm', 'ps_auxcww'], priority=1) self.add_cmd_output("pstree -lp", root_symlink="pstree") if self.get_option("lsof"): self.add_cmd_output("lsof +M -n -l -c ''", root_symlink="lsof", timeout=15, priority=50, tags="lsof") if self.get_option("lsof-threads"): self.add_cmd_output("lsof +M -n -l", timeout=15, priority=50) self.add_cmd_output([ "ps alxwww", "ps -elfL" ], cmd_as_tag=True) self.add_cmd_output([ f"{ps_axo} {ps_group_opts}", f"{ps_axo} {ps_sched_opts}", ]) if self.get_option("samples"): self.add_cmd_output("iotop -b -o -d 0.5 -t -n " f"{self.get_option('samples')}", priority=100) self.add_cmd_output([ "pidstat -p ALL -rudvwsRU --human -h", "pidstat -tl" ]) def collect(self): with self.collection_file('pids_to_packages.json') as pfile: if not self.policy.package_manager.query_path_command: pfile.write('Package manager not configured for path queries') return _ps = self.exec_cmd('ps --no-headers aex') pidpkg = {} paths = {} if not _ps['status'] == 0: pfile.write(f"Unable to get process list: {_ps['output']}") return for proc in _ps['output'].splitlines(): proc = proc.strip().split() pid = proc[0] path = proc[4] if not self.path_exists(path): continue if path not in paths: paths[path] = self.policy.package_manager.pkg_by_path(path) pidpkg[pid] = {'path': path, 'package': paths[path]} pfile.write(json.dumps(pidpkg, indent=4)) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/migration_results.py0000664000175000017500000000117514660147624020717 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class MigrationResults(Plugin, RedHatPlugin): short_desc = 'Information about conversions and upgrades' plugin_name = 'migration_results' profiles = ('system',) files = ('/etc/migration-results',) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/microovn.py0000664000175000017500000000221714660147624016777 0ustar arifarif# Copyright (C) 2024 Alan Baghumian # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class MicroOVN(Plugin, UbuntuPlugin): """The MicroOVN plugin collects the current status of the microovn snap. It will collect journald logs as well as output from various microovn commands. """ short_desc = 'MicroOVN Snap' plugin_name = "microovn" profiles = ('network', 'virt') packages = ('microovn', ) commands = ('microovn', ) def setup(self): self.add_journal(units="snap.microovn.*") microovn_subcmds = [ 'cluster list', 'status', 'certificates list', '--version' ] self.add_cmd_output([ f"microovn {subcmd}" for subcmd in microovn_subcmds ]) sos-4.8.0/sos/report/plugins/cxl.py0000664000175000017500000000250314660147624015727 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Cxl(Plugin, IndependentPlugin): """This plugin collects data from Compute Express Link (CXL) devices """ short_desc = 'Compute Express Link (CXL)' plugin_name = 'cxl' profiles = ('storage', 'hardware', 'memory') # Utilities can be installed by package or self compiled packages = ('cxl-cli', 'daxctl') commands = ('cxl', 'daxctl') def setup(self): """ Use the daxctl-list(1) command to collect disabled, devices, mapping, and region information Output is JSON formatted """ self.add_cmd_output([ "daxctl version", "daxctl list", "daxctl list -iDRM" ]) # Use the cxl-list(1) command to collect data about all CXL devices. # Output is JSON formatted. self.add_cmd_output([ "cxl version", "cxl list", "cxl list -vvv" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ds.py0000664000175000017500000000715214660147624015554 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Kent Lamb # Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # Copyright (C) 2021 Red Hat, Inc., Mark Reynolds # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class DirectoryServer(Plugin, RedHatPlugin): short_desc = 'Directory Server' plugin_name = 'ds' profiles = ('identity',) files = ('/etc/dirsrv', '/opt/redhat-ds') packages = ('redhat-ds-base', 'redhat-ds-7') def check_version(self): """ Get Directory Server version """ if self.is_installed("redhat-ds-base") or \ self.path_exists("/etc/dirsrv"): return "ds8" if self.is_installed("redhat-ds-7") or \ self.path_exists("/opt/redhat-ds"): return "ds7" return False def setup(self): self.add_forbidden_path([ "/etc/dirsrv/slapd*/pin.txt", "/etc/dirsrv/slapd*/key3.db", "/etc/dirsrv/slapd*/pwfile.txt", "/etc/dirsrv/slapd*/*passw*", "/etc/dirsrv/admin-serv/key[3-4].db", "/etc/dirsrv/admin-serv/admpw", "/etc/dirsrv/admin-serv/password.conf" ]) try: for dsrv in self.listdir("/etc/dirsrv"): if dsrv[0:5] == 'slapd': certpath = self.path_join("/etc/dirsrv", dsrv) self.add_cmd_output(f"certutil -L -d {certpath}") self.add_cmd_output(f"dsctl {dsrv} healthcheck") except OSError: self._log_warn("could not list /etc/dirsrv") if not self.check_version(): self.add_alert("Directory Server not found.") elif "ds8" in self.check_version(): self.add_copy_spec([ "/etc/dirsrv/slapd*/cert8.db", "/etc/dirsrv/slapd*/certmap.conf", "/etc/dirsrv/slapd*/dse.ldif", "/etc/dirsrv/slapd*/dse.ldif.startOK", "/etc/dirsrv/slapd*/secmod.db", "/etc/dirsrv/slapd*/schema/*.ldif", "/etc/dirsrv/admin-serv", "/var/log/dirsrv/*" ]) self.add_file_tags({ "/var/log/dirsrv/*/access": "dirsrv_access" }) elif "ds7" in self.check_version(): self.add_copy_spec([ "/opt/redhat-ds/slapd-*/config", "/opt/redhat-ds/slapd-*/logs" ]) self.add_dir_listing("/var/lib/dirsrv/slapd-*/db/*") def postproc(self): # Example for scrubbing rootpw hash # # nsslapd-rootpw: AAAAB3NzaC1yc2EAAAADAQABAAABAQDeXYA3juyPqaUuyfWV2HuIM # v3gebb/5cvx9ehEAFF2yIKvsQN2EJGTV+hBM1DEOB4eyy/H11NqcNwm/2QsagDB3PVwYp # 9VKN3BdhQjlhuoYKhLwgtYUMiGL8AX5g1qxjirIkTRJwjbXkSNuQaXig7wVjmvXnB2o7B # zLtu99DiL1AizfVeZTYA+OVowYKYaXYljVmVKS+g3t29Obaom54ZLpfuoGMmyO64AJrWs # # to # # nsslapd-rootpw:******** regexppass = r"(nsslapd-rootpw(\s)*:(\s)*)(\S+)([\r\n]\s.*)*\n" regexpkey = r"(nsSymmetricKey(\s)*::(\s)*)(\S+)([\r\n]\s.*)*\n" repl = r"\1********\n" self.do_path_regex_sub('/etc/dirsrv/*', regexppass, repl) self.do_path_regex_sub('/etc/dirsrv/*', regexpkey, repl) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/keepalived.py0000664000175000017500000000152714660147624017257 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Keepalived(Plugin, IndependentPlugin): short_desc = 'Keepalived routing server' plugin_name = 'keepalived' profiles = ('webserver', 'network', 'cluster') packages = ('keepalived',) def setup(self): self.add_copy_spec([ "/etc/keepalived/keepalived.conf", "/etc/sysconfig/keepalived" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/infinidat.py0000664000175000017500000000260614660147624017112 0ustar arifarif# Copyright (C) 2024 Alejandro Santoyo # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class InfinidatStorage(Plugin, IndependentPlugin): short_desc = 'Infinidat Storage plugin' plugin_name = 'infinidat' profiles = ('storage',) packages = ('host-power-tools',) def setup(self): # Get infinidat logs if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/infinihost.latest*.log", "/var/log/infinihost.usage*.log", ]) else: self.add_copy_spec([ "/var/log/infinihost*.log", "/var/log/buildout.*.log", ]) # Get info from the infinidat boxes, etc. self.add_cmd_output([ "infinihost volume list", "infinihost connectivity list", "infinihost system list", "infinihost pool list", "infinihost snapshot list", "infinihost --version" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/opencl.py0000664000175000017500000000126414660147624016424 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OpenCL(Plugin, IndependentPlugin): short_desc = 'OpenCL' plugin_name = 'opencl' profiles = ('hardware', 'desktop', 'gpu') files = ('/usr/bin/clinfo',) def setup(self): self.add_cmd_output([ "clinfo", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/samba.py0000664000175000017500000000341414660147624016226 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Samba(Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin): short_desc = 'Samba Windows interoperability' packages = ('samba-common',) plugin_name = "samba" profiles = ('services',) def setup(self): self.add_copy_spec([ "/etc/samba/smb.conf", "/etc/samba/lmhosts", ]) self.add_copy_spec("/var/log/samba/log.smbd") self.add_copy_spec("/var/log/samba/log.nmbd") self.add_copy_spec("/var/log/samba/log.winbindd") self.add_copy_spec("/var/log/samba/log.winbindd-idmap") self.add_copy_spec("/var/log/samba/log.winbindd-dc-connect") self.add_copy_spec("/var/log/samba/log.wb-*") if self.get_option("all_logs"): self.add_copy_spec("/var/log/samba/") self.add_cmd_output("testparm -s", tags="testparm_s") self.add_cmd_output([ "wbinfo --domain='.' --domain-users", "wbinfo --domain='.' --domain-groups", "wbinfo --trusted-domains --verbose", "wbinfo --check-secret", "wbinfo --online-status", "net primarytrust dumpinfo", "net ads info", "net ads testjoin", ]) class RedHatSamba(Samba, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec("/etc/sysconfig/samba") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/shmcli.py0000664000175000017500000001033214660147624016417 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class SHMcli(Plugin, IndependentPlugin): """shmcli pulls hardware information from PowerVault/Dell Storage JBOD's attached to server. It provides information of the adapters, emms, drives, enclosures, fans, power supplies and the sensory data of temp, voltage, and current sensors. """ short_desc = 'Dell Server Hardware Manager' plugin_name = 'shmcli' profiles = ('system', 'storage', 'hardware',) shmcli_bin = "/opt/dell/ServerHardwareManager/" \ "ServerHardwareManagerCLI/bin/shmcli" files = (shmcli_bin,) option_list = [ PluginOpt('debug', default=False, desc='capture support debug data') ] def setup(self): subcmds = [ 'list adapters', 'list physical enclosures', 'list failed drives' ] for subcmd in subcmds: self.add_cmd_output( f"{self.shmcli_bin} {subcmd}", suggest_filename=f"shmcli_{subcmd}") self.collect_enclosures_list() self.collect_drivers_list() def collect_enclosures_list(self): """ Collect info on the enclosures """ models = [] # Get the storage hardware models result = self.exec_cmd('lsscsi -g') if result['status'] == 0: for line in result['output'].splitlines(): words = line.split() if (len(words) > 2 and words[2].upper() == 'DELL'): models.append(line.split()[3]) models = list(set(models)) subcmds = [ 'list emms', 'list drawers', 'list emm slots', 'list drive slots', 'list fans', 'list temp sensors', 'list voltage sensors', 'list current sensors', 'list power supplies', 'info enclosure' ] result = self.collect_cmd_output( f'{self.shmcli_bin} list enclosures', suggest_filename='shmcli_list_enclosures' ) if result['status'] == 0: for line in result['output'].splitlines()[2:-2]: line = line.split() if any(m in line for m in models): adapt_index = line[-1] enc_index = line[0] for subcmd in subcmds: _cmd = (f"{self.shmcli_bin} {subcmd} -a={adapt_index}" f" -enc={enc_index}") _fname = _cmd.replace(self.shmcli_bin, 'shmcli') self.add_cmd_output(_cmd, suggest_filename=_fname) if self.get_option('debug'): logpath = self.get_cmd_output_path(make=False) _dcmd = (f"{self.shmcli_bin} getdebugcli " f"-a={adapt_index} -enc={enc_index}") _dname = _dcmd.replace(self.shmcli_bin, 'shmcli') _odir = f" -outputdir={logpath}" self.add_cmd_output( _dcmd + _odir, suggest_filename=_dname, timeout=300 ) def collect_drivers_list(self): """ Collect info on the drives """ result = self.collect_cmd_output( f'{self.shmcli_bin} list drives', suggest_filename='shmcli_list_drives' ) if result['status'] == 0: for line in result['output'].splitlines(): words = line.split() if len(words) > 6: if (words[0] not in ['WWN', '---']): _cmd = f"{self.shmcli_bin} info drive -d={words[0]}" _fname = _cmd.replace(self.shmcli_bin, 'shmcli') self.add_cmd_output(_cmd, suggest_filename=_fname) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/grub2.py0000664000175000017500000000547214660147624016172 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate class Grub2(Plugin, IndependentPlugin): short_desc = 'GRUB2 bootloader' plugin_name = 'grub2' profiles = ('boot',) packages = ('grub2', 'grub2-efi', 'grub2-common') def setup(self): self.add_file_tags({ '/boot/grub2/grub.cfg': 'grub2_cfg', '/boot/efi/.*/grub.cfg': 'grub2_efi_cfg', '/boot/grub2/grubenv': 'grubenv' }) self.add_copy_spec([ "/boot/efi/EFI/*/grub.cfg", "/boot/grub2/grub.cfg", "/boot/grub2/grubenv", "/boot/grub/grub.cfg", "/boot/loader/entries", "/boot/grub2/custom.cfg", "/boot/grub2/user.cfg", "/etc/default/grub", "/etc/grub2.cfg", "/etc/grub.d", "/etc/grub2-efi.cfg" ]) # call grub2-mkconfig with GRUB_DISABLE_OS_PROBER=true to prevent # possible unwanted loading of some kernel modules # further, check if the command supports --no-grubenv-update option # to prevent removing of extra args in $kernel_opts, and (only) if so, # call the command with this argument grub_cmd = 'grub2-mkconfig' out = {'cmd': f'{grub_cmd} --help', 'output': '--no-grubenv-update'} if self.test_predicate(self, pred=SoSPredicate(self, cmd_outputs=out)): grub_cmd += ' --no-grubenv-update' self.add_cmd_output(grub_cmd, env={'GRUB_DISABLE_OS_PROBER': 'true'}, pred=SoSPredicate(self, kmods=['dm_mod'])) def postproc(self): # the trailing space is required; python treats '_' as whitespace # causing the passwd_exp to match pbkdf2 passwords and mangle them. passwd_exp = r"(password )\s*(\S*)\s*(\S*)" passwd_pbkdf2_exp = r"(password_pbkdf2)\s*(\S*)\s*(\S*)" passwd_sub = r"\1 \2 ********" passwd_pbkdf2_sub = r"\1 \2 grub.pbkdf2.********" self.do_cmd_output_sub( "grub2-mkconfig", passwd_pbkdf2_exp, passwd_pbkdf2_sub ) self.do_cmd_output_sub( "grub2-mkconfig", passwd_exp, passwd_sub ) self.do_path_regex_sub( r".*\/grub\.", passwd_exp, passwd_sub ) self.do_path_regex_sub( r".*\/grub\.", passwd_pbkdf2_exp, passwd_pbkdf2_sub ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/nfsganesha.py0000664000175000017500000000231414660147624017256 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class NfsGanesha(Plugin, IndependentPlugin): short_desc = 'NFS-Ganesha file server information' plugin_name = 'nfsganesha' profiles = ('storage', 'network', 'nfs') packages = ('nfs-ganesha',) def setup(self): self.add_copy_spec([ "/etc/ganesha", "/etc/sysconfig/ganesha", "/run/sysconfig/ganesha", "/var/log/ganesha/*.log" ]) if self.get_option("all_logs"): # Grab rotated logs as well self.add_copy_spec("/var/log/ganesha/*.log*") self.add_cmd_output([ "dbus-send --type=method_call --print-reply" " --system --dest=org.ganesha.nfsd " "/org/ganesha/nfsd/ExportMgr " "org.ganesha.nfsd.exportmgr.ShowExports" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ceph_rgw.py0000664000175000017500000001100714660147624016736 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from socket import gethostname from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class CephRGW(Plugin, RedHatPlugin, UbuntuPlugin): short_desc = 'CEPH rgw' plugin_name = 'ceph_rgw' profiles = ('storage', 'virt', 'container', 'webserver', 'ceph') containers = ('ceph-(.*)?rgw.*',) files = ('/var/lib/ceph/radosgw/*', '/var/snap/microceph/common/data/radosgw/*') def setup(self): all_logs = self.get_option("all_logs") cmds = ['bucket limit check', 'bucket list', 'bucket stats', 'datalog list', 'datalog status', 'gc list', 'lc list', 'log list', 'metadata sync status', 'period list', 'realm list', 'reshard list', 'sync error list', 'sync status', 'zone list', 'zone placement list', 'zonegroup list', 'zonegroup placement list', ] microceph = self.policy.package_manager.pkg_by_name('microceph') if microceph: if all_logs: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-radosgw*.log*", ]) else: self.add_copy_spec([ "/var/snap/microceph/common/logs/*ceph-radosgw*.log", ]) self.add_forbidden_path([ "/var/snap/microceph/common/**/*keyring*", "/var/snap/microceph/current/**/*keyring*", "/var/snap/microceph/common/state/*", ]) else: if not all_logs: self.add_copy_spec('/var/log/ceph/ceph-client.rgw*.log', tags='ceph_rgw_log') else: self.add_copy_spec('/var/log/ceph/ceph-client.rgw*.log*', tags='ceph_rgw_log') self.add_forbidden_path([ "/etc/ceph/*keyring*", "/var/lib/ceph/*keyring*", "/var/lib/ceph/*/*keyring*", "/var/lib/ceph/*/*/*keyring*", "/var/lib/ceph/osd", "/var/lib/ceph/mon", # Excludes temporary ceph-osd mount location like # /var/lib/ceph/tmp/mnt.XXXX from sos collection. "/var/lib/ceph/tmp/*mnt*", "/etc/ceph/*bindpass*" ]) # Get commands output for both Ceph and microCeph rgw_id = "radosgw.gateway" if microceph else "rgw." + gethostname() self.add_cmd_output([f"radosgw-admin --id={rgw_id} {c}" for c in cmds]) # Get all the zone data res = self.collect_cmd_output(f'radosgw-admin --id={rgw_id} zone list') if res['status'] == 0: try: _out = json.loads(res['output']) zone_list = _out['zones'] for zone in zone_list: self.add_cmd_output(f'radosgw-admin --id={rgw_id} ' f'zone get --rgw-zone={zone}') except ValueError as err: self._log_error(f'Error while getting get rgw ' f'zone list: {err}') # Get all the zonegroup data res = self.collect_cmd_output(f'radosgw-admin --id={rgw_id} ' f'zonegroup list') if res['status'] == 0: try: _out = json.loads(res['output']) zonegroups = _out['zonegroups'] for zgroup in zonegroups: self.add_cmd_output(f'radosgw-admin --id={rgw_id} ' f'zone get --rgw-zonegroup={zgroup}') except ValueError as err: self._log_error(f'Error while getting get rgw ' f'zonegroup list: {err}') def postproc(self): """ Obfuscate secondary zone access keys """ rsub = r'("access_key":|"secret_key":)\s.*' self.do_cmd_output_sub("radosgw-admin", rsub, r'\1 "**********"') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rpmostree.py0000664000175000017500000000217314660147624017164 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Rpmostree(Plugin, RedHatPlugin): short_desc = 'rpm-ostree image/package system' plugin_name = 'rpmostree' packages = ('rpm-ostree',) def setup(self): self.add_copy_spec('/etc/ostree/remotes.d/') subcmds = [ 'status --json', 'kargs', 'db list', 'db diff', '--version' ] self.add_cmd_output([f"rpm-ostree {subcmd}" for subcmd in subcmds]) units = [ 'rpm-ostreed', 'rpm-ostreed-automatic', 'rpm-ostree-bootstatus' ] for unit in units: self.add_journal(unit) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/landscape.py0000664000175000017500000000625414660147624017102 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, UbuntuPlugin class Landscape(Plugin, UbuntuPlugin): short_desc = 'Ubuntu Landscape client' plugin_name = 'landscape' profiles = ('sysmgmt',) files = ('/etc/landscape/client.conf', '/etc/landscape/service.conf') packages = ('landscape-client', 'landscape-server') def setup(self): vars_all = [p in os.environ for p in [ 'LANDSCAPE_API_KEY', 'LANDSCAPE_API_SECRET', 'LANDSCAPE_API_URI', ]] if not all(vars_all): self.soslog.warning("Not all environment variables set. " "Source the environment file for the user " "intended to connect to the Landscape " "environment so that the landscape-api " "commands can be used.") else: self.add_cmd_output([ "landscape-api get-distributions", "landscape-api get-apt-sources", "landscape-api get-repository-profiles", "landscape-api get activites --limit 100", ]) self.add_cmd_output([ "landscape-api --json get-distributions", "landscape-api --json get-apt-sources", "landscape-api --json get-repository-profiles", "landscape-api --json get activites --limit 100", ]) self.add_copy_spec([ "/etc/default/landscape-client", "/etc/default/landscape-server", "/etc/landscape/client.conf", "/etc/landscape/service.conf", "/etc/landscape/service.conf.old", "/var/lib/landscape/landscape-oops/*/OOPS-*" ]) if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/landscape/*.log", "/var/log/landscape-server/*.log", ]) else: self.add_copy_spec([ "/var/log/landscape", "/var/log/landscape-server" ]) self.add_cmd_output([ "gpg --verify /etc/landscape/license.txt", "head -n 5 /etc/landscape/license.txt", "lsctl status" ]) def postproc(self): self.do_file_sub( "/etc/landscape/client.conf", r"registration_password(.*)", r"registration_password[********]" ) keys = [ "password", "store_password", "secret-token", "oidc-client-secret", "oidc-client-id", ] self.do_path_regex_sub( "/etc/landscape/service.conf*", fr"({'|'.join(keys)}) = (.*)", r"\1 = [********]" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/sos_extras.py0000664000175000017500000000736314660147624017344 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import stat from sos.report.plugins import Plugin, IndependentPlugin class SosExtras(Plugin, IndependentPlugin): short_desc = 'Collect extra data defined in /etc/sos/extras.d' """The plugin traverses 'extras_dir' directory and for each file there, it executes commands or collects files optionally with sizelimit. Expected content of a file: - empty lines or those starting with '#' are ignored - add_copy_spec called to lines starting by ':', optionally followed by sizelimit - otherwise, whole line will be executed as a command. Example: command1 --arg1 val1 command2 :/path/to/file :/path/to/files* sizelimit WARNING: be careful what files to collect or what commands to execute: - prevent calling potentially dangerous or system altering commands, like: - using multiple commands on a line (via pipes, semicolon etc.) - executing commands on background - setting env.variables (as those will be ignored) - altering a system (not only by "rm -rf") - be aware, no secret obfuscation is made """ plugin_name = "sos_extras" extras_dir = '/etc/sos/extras.d/' files = (extras_dir,) def setup(self): try: st_res = os.stat(self.extras_dir) if (st_res.st_uid != 0) or (st_res.st_mode & stat.S_IWGRP) or \ (st_res.st_mode & stat.S_IWOTH): self._log_warn(f"Skipping sos extras as {self.extras_dir} has" " too wide permissions or ownership.") return except OSError: self._log_warn(f"can't stat {self.extras_dir}, skipping sos" " extras") return for path, _, filelist in os.walk(self.extras_dir): for file in filelist: _file = self.path_join(path, file) self._log_warn(f"Collecting data from extras file {_file}") try: with open(_file, 'r', encoding='UTF-8') as sfile: for line in sfile.read().splitlines(): # ignore empty lines or comments if len(line.split()) == 0 or line.startswith('#'): continue # lines starting by ':' specify file pattern to # collect optionally followed by sizelimit if line.startswith(':'): words = line.split() limit = None if len(words) > 1: try: limit = int(words[1]) except ValueError: self._log_warn( f"Can't decode size limit on line" f"{line} in {_file}, using default" ) self.add_copy_spec(words[0][1:], sizelimit=limit) else: # command to execute self.add_cmd_output(line, subdir=file) except IOError: self._log_warn(f"unable to read extras file {_file}") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/boot.py0000664000175000017500000000371514660147624016112 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Boot(Plugin, IndependentPlugin): short_desc = 'Bootloader information' plugin_name = 'boot' profiles = ('system', 'boot') packages = ('grub', 'grub2', 'grub-common', 'grub2-common', 'zipl') option_list = [ PluginOpt("all-images", default=False, desc="collect lsinitrd for all images") ] def setup(self): self.add_copy_spec([ # legacy / special purpose bootloader configs "/etc/milo.conf", "/etc/silo.conf", "/boot/efi/efi/redhat/elilo.conf", "/etc/yaboot.conf", "/boot/yaboot.conf" ]) self.add_dir_listing('/boot', tags=['ls_boot'], recursive=True) self.add_dir_listing('/sys/firmware/', tags=['ls_sys_firmware'], recursive=True) self.add_dir_listing(['/initrd.img', '/boot/initrd.img']) self.add_cmd_output("lsinitrd", tags="lsinitrd") self.add_cmd_output("mokutil --sb-state", tags="mokutil_sbstate") self.add_cmd_output([ "efibootmgr -v", "lsinitramfs -l /initrd.img", "lsinitramfs -l /boot/initrd.img" ]) if self.get_option("all-images"): for image in glob('/boot/initr*.img*'): if image[-9:] == "kdump.img": continue self.add_cmd_output(f"lsinitrd {image}", priority=100) self.add_cmd_output(f"lsinitramfs -l {image}", priority=100) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/date.py0000664000175000017500000000154314660147624016061 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Date(Plugin, IndependentPlugin): short_desc = 'Basic system time information' plugin_name = 'date' def setup(self): self.add_cmd_output([ 'date', 'date --utc', 'hwclock' ], cmd_as_tag=True) self.add_copy_spec([ '/etc/localtime', '/etc/adjtime', ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ctdb.py0000664000175000017500000000265614660147624016066 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Ctdb(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'Samba Clustered TDB' packages = ('ctdb',) profiles = ('cluster', 'storage') plugin_name = "ctdb" def setup(self): self.add_copy_spec([ "/etc/ctdb/ctdb.conf", "/etc/ctdb/*.options", "/etc/ctdb/nodes", "/etc/ctdb/public_addresses", "/etc/ctdb/static-routes", "/etc/ctdb/multipathd", "/var/log/log.ctdb" ]) self.add_cmd_output([ "ctdb ip", "ctdb ping", "ctdb status", "ctdb ifaces", "ctdb listnodes", "ctdb listvars", "ctdb statistics", "ctdb getdbmap", "ctdb event script list legacy" ]) class RedHatCtdb(Ctdb, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec("/etc/sysconfig/ctdb") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/python.py0000664000175000017500000000744714660147624016476 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc.,Poornima M. Kshirsagar # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import hashlib import json import os from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) from sos.policies.distros.redhat import RHELPolicy class Python(Plugin): """Captures information on the installed python runtime(s), as well as python modules installed via pip. """ short_desc = 'Python runtime' plugin_name = 'python' profiles = ('system',) packages = ('python',) python_version = "python -V" def setup(self): self.add_cmd_output( self.python_version, suggest_filename="python-version" ) pips = self.exec_cmd("whereis pip -b") if pips['status'] == 0: # output is like: # pip: /usr/bin/pip2.7 /usr/bin/pip3.6 # where we must skip the first word for pip in pips['output'].split()[1:]: self.add_cmd_output(f"{pip} list installed") class UbuntuPython(Python, DebianPlugin, UbuntuPlugin): python_version = "python3 -V" packages = ('python3',) class RedHatPython(Python, RedHatPlugin): """In addition to the base information, on Red Hat family distributions the python plugin also supports the 'hashes' option. If enabled, this plugin will generate a json-formatted listing of all pyfiles within the distribution-standard python package installation locations. """ packages = ('python', 'python36', 'python2', 'python3', 'platform-python') option_list = [ PluginOpt('hashes', default=False, desc='collect hashes for all python files') ] def setup(self): self.add_cmd_output(['python2 -V', 'python3 -V']) if isinstance(self.policy, RHELPolicy) and \ self.policy.dist_version() == 8: self.python_version = "/usr/libexec/platform-python -V" super().setup() def collect(self): if self.get_option('hashes'): with self.collection_file('digests.json') as hfile: hfile.write(json.dumps(self.get_hashes(), indent=4)) def get_hashes(self): """ Get the hashes for Python files """ digests = { 'digests': [] } py_paths = [ '/usr/lib', '/usr/lib64', '/usr/local/lib', '/usr/local/lib64' ] for py_path in py_paths: for root, _, files in os.walk(self.path_join(py_path)): for _file in files: if not _file.endswith('.py'): continue filepath = self.path_join(root, _file) try: with open(filepath, 'rb') as file: digest = hashlib.sha256() data = file.read(1024) while data: digest.update(data) data = file.read(1024) digest = digest.hexdigest() digests['digests'].append({ 'filepath': filepath, 'sha256': digest }) except IOError: self._log_error("Unable to read python file at " f"{filepath}") return digests # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kernel.py0000664000175000017500000001330014660147624016416 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob from sos.policies.distros.redhat import RedHatPolicy from sos.report.plugins import Plugin, IndependentPlugin, PluginOpt class Kernel(Plugin, IndependentPlugin): """The Kernel plugin is aimed at collecting general information about the locally running kernel. This information should be distribution-neutral using commands and filesystem collections that are ubiquitous across distributions. Debugging information from /sys/kernel/debug is collected by default, however care is taken so that these collections avoid areas like /sys/kernel/debug/tracing/trace_pipe which would otherwise cause the sos collection attempt to appear to 'hang'. The 'trace' option will enable the collection of the /sys/kernel/debug/tracing/trace file specfically, but will not change the behavior stated above otherwise. """ short_desc = 'Linux kernel' plugin_name = 'kernel' profiles = ('system', 'hardware', 'kernel') verify_packages = ('kernel$',) sys_module = '/sys/module' option_list = [ PluginOpt('with-timer', default=False, desc='gather /proc/timer* statistics'), PluginOpt('trace', default=False, desc='gather /sys/kernel/debug/tracing/trace file') ] def setup(self): # RedHat distributions can deliver kernel in RPM named either 'kernel' # or 'kernel-redhat', so we must verify both if isinstance(self.policy, RedHatPolicy): self.verify_packages = ('kernel$', 'kernel-redhat$') # compat self.add_cmd_output("uname -a", root_symlink="uname", tags="uname") self.add_cmd_output("lsmod", root_symlink="lsmod", tags="lsmod") self.add_dir_listing('/sys/kernel/slab') try: modules = self.listdir(self.sys_module) self.add_cmd_output("modinfo " + " ".join(modules), suggest_filename="modinfo_ALL_MODULES", tags='modinfo_all') except OSError: self._log_warn(f"could not list {self.sys_module}") # find /lib/modules/*/{extras,updates,weak-updates} -ls extra_mod_patterns = [ "/lib/modules/*/extra", "/lib/modules/*/updates", "/lib/modules/*/weak-updates", ] extra_mod_paths = [] for pattern in extra_mod_patterns: extra_mod_paths.extend(glob.glob(pattern)) if extra_mod_paths: self.add_cmd_output(f"find {' '.join(extra_mod_paths)} -ls") self.add_cmd_output([ "dmesg", "dmesg -T", "dkms status" ], cmd_as_tag=True) self.add_cmd_output("sysctl -a", tags="sysctl") clocksource_path = "/sys/devices/system/clocksource/clocksource0/" self.add_forbidden_path([ '/sys/kernel/debug/tracing/trace_pipe', '/sys/kernel/debug/tracing/README', '/sys/kernel/debug/tracing/trace_stat', '/sys/kernel/debug/tracing/per_cpu', '/sys/kernel/debug/tracing/events', '/sys/kernel/debug/tracing/free_buffer', '/sys/kernel/debug/tracing/trace_marker', '/sys/kernel/debug/tracing/trace_marker_raw', '/sys/kernel/debug/tracing/instances/*/per_cpu/*/snapshot_raw', '/sys/kernel/debug/tracing/instances/*/per_cpu/*/trace_pipe*', '/sys/kernel/debug/tracing/instances/*/trace_pipe' ]) self.add_copy_spec([ "/proc/modules", "/proc/sys/kernel/random/boot_id", "/sys/module/*/parameters", "/sys/module/*/initstate", "/sys/module/*/refcnt", "/sys/module/*/taint", "/sys/module/*/version", "/sys/firmware/acpi/*", "/sys/kernel/debug/tracing/*", "/sys/kernel/livepatch/*", "/proc/kallsyms", "/proc/buddyinfo", "/proc/slabinfo", "/proc/zoneinfo", f"/lib/modules/{self.policy.kernel_version()}/modules.dep", "/etc/conf.modules", "/etc/modules.conf", "/etc/modprobe.conf", "/etc/modprobe.d", "/lib/modprobe.d", "/run/modprobe.d", "/usr/local/lib/modprobe.d", "/etc/sysctl.conf", "/etc/sysctl.d", "/lib/sysctl.d", "/proc/cmdline", "/proc/driver", "/proc/sys/kernel/tainted", "/proc/softirqs", "/proc/lock*", "/proc/misc", "/var/log/dmesg", "/sys/fs/pstore", "/var/lib/systemd/pstore", "/sys/kernel/debug/dynamic_debug/control", "/sys/kernel/debug/extfrag/unusable_index", "/sys/kernel/debug/extfrag/extfrag_index", "/sys/kernel/debug/hv-balloon", clocksource_path + "available_clocksource", clocksource_path + "current_clocksource", "/proc/pressure/", f"/boot/config-{self.policy.kernel_version()}" ]) if self.get_option("with-timer"): # This can be very slow, depending on the number of timers, # and may also cause softlockups self.add_copy_spec("/proc/timer*") if not self.get_option("trace"): self.add_forbidden_path("/sys/kernel/debug/tracing/trace") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/services.py0000664000175000017500000000267514660147624016776 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin, PluginOpt) class Services(Plugin): short_desc = 'System services' plugin_name = "services" profiles = ('system', 'boot') option_list = [ PluginOpt('servicestatus', default=False, desc='collect status of all running services') ] def setup(self): self.add_copy_spec([ "/etc/inittab", "/etc/rc.d", "/etc/rc.local" ]) if self.get_option('servicestatus'): self.add_cmd_output("service --status-all") self.add_cmd_output("/sbin/runlevel") self.add_dir_listing('/var/lock/subsys') class RedHatServices(Services, RedHatPlugin): def setup(self): super().setup() self.add_cmd_output("chkconfig --list", root_symlink="chkconfig", tags="chkconfig") class DebianServices(Services, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec("/etc/rc*.d") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rabbitmq.py0000664000175000017500000000535214660147624016747 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class RabbitMQ(Plugin, IndependentPlugin): short_desc = 'RabbitMQ messaging service' plugin_name = 'rabbitmq' profiles = ('services',) var_puppet_gen = "/var/lib/config-data/puppet-generated/rabbitmq" files = ( '/etc/rabbitmq/rabbitmq.conf', var_puppet_gen + '/etc/rabbitmq/rabbitmq.config' ) packages = ('rabbitmq-server',) def setup(self): in_container = False container_names = [] _containers = self.get_containers() for _con in _containers: if _con[1].startswith('rabbitmq'): in_container = True container_names.append(_con[1]) if in_container: for container in container_names: self.add_container_logs(container) self.add_cmd_output( 'rabbitmqctl report', container=container, foreground=True, tags="rabbitmq_report" ) self.add_cmd_output( "rabbitmqctl eval 'rabbit_diagnostics:maybe_stuck().'", container=container, foreground=True, timeout=10 ) else: self.add_cmd_output("rabbitmqctl report") self.add_cmd_output( "rabbitmqctl eval 'rabbit_diagnostics:maybe_stuck().'", timeout=10) self.add_copy_spec([ "/etc/rabbitmq/*", self.var_puppet_gen + "/etc/rabbitmq/*", self.var_puppet_gen + "/etc/security/limits.d/", self.var_puppet_gen + "/etc/systemd/" ]) self.add_copy_spec([ "/var/log/rabbitmq/*", ]) self.add_file_tags({ "/var/log/rabbitmq/rabbit@.*[^-sasl].log": "rabbitmq_logs", "/var/log/rabbitmq/startup_err": "rabbitmq_startup_err" }) # Crash dump can be large in some situation but it is useful to # investigate why rabbitmq crashes. So capture the file without # sizelimit self.add_copy_spec([ "/var/log/containers/rabbitmq/erl_crash.dump" ], sizelimit=0) def postproc(self): self.do_file_sub("/etc/rabbitmq/rabbitmq.conf", r"(\s*default_pass\s*,\s*)\S+", r"\1<<***>>},") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_database.py0000664000175000017500000000474414660147624020765 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class OpenStackDatabase(Plugin): short_desc = 'Openstack Database Information' plugin_name = 'openstack_database' profiles = ('openstack', 'openstack_controller') option_list = [ PluginOpt('dump', default=False, desc='Dump select databases'), PluginOpt('dumpall', default=False, desc='Dump ALL databases') ] databases = [ 'cinder', 'glance', 'heat', 'ironic', 'keystone', 'mistral', '(.*)?neutron', 'nova.*' ] def setup(self): # determine if we're running databases on the host or in a container _db_containers = [ 'galera-bundle-.*', # overcloud 'mysql' # undercloud ] cname = None for container in _db_containers: cname = self.get_container_by_name(container) if cname: break fname = f"clustercheck_{cname}" if cname else None self.add_cmd_output('clustercheck', container=cname, timeout=15, suggest_filename=fname) if self.get_option('dump') or self.get_option('dumpall'): db_dump = self.get_mysql_db_string(container=cname) db_cmd = f"mysqldump --opt {db_dump}" self.add_cmd_output(db_cmd, suggest_filename='mysql_dump.sql', sizelimit=0, container=cname) def get_mysql_db_string(self, container=None): """ Get mysql DB command to be dumped """ if self.get_option('dumpall'): return '--all-databases' collect = [] dbs = self.exec_cmd('mysql -e "show databases;"', container=container) for database in dbs['output'].splitlines(): if any(re.match(database, reg) for reg in self.databases): collect.append(database) return '-B ' + ' '.join(d for d in collect) class RedHatOpenStackDatabase(OpenStackDatabase, RedHatPlugin): packages = ('openstack-selinux', ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/soundcard.py0000664000175000017500000000212114660147624017117 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Soundcard(Plugin): short_desc = 'Sound devices' plugin_name = "soundcard" profiles = ('desktop', 'hardware') def setup(self): self.add_copy_spec("/proc/asound/*") self.add_cmd_output([ "aplay -l", "aplay -L", "amixer" ]) class RedHatSoundcard(Soundcard, RedHatPlugin): def setup(self): super().setup() self.add_copy_spec([ "/etc/alsa/*", "/etc/asound.*" ]) class DebianSoundcard(Soundcard, DebianPlugin, UbuntuPlugin): def setup(self): super().setup() self.add_copy_spec("/etc/pulse/*") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rasdaemon.py0000664000175000017500000000174214660147624017116 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Rasdaemon(Plugin, RedHatPlugin): short_desc = 'rasdaemon kernel trace event monitor' plugin_name = 'rasdaemon' packages = ('rasdaemon', ) services = ('rasdaemon', ) def setup(self): subcmds = [ '--errors', '--guess-labels', '--layout', '--mainboard', '--print-labels', '--status', '--summary' ] self.add_cmd_output([f"ras-mc-ctl {sub}" for sub in subcmds]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/logs.py0000664000175000017500000001107114660147624016105 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob from sos.report.plugins import Plugin, PluginOpt, IndependentPlugin, CosPlugin class LogsBase(Plugin): short_desc = 'System logs' plugin_name = "logs" profiles = ('system', 'hardware', 'storage') def setup(self): rsyslog = 'etc/rsyslog.conf' confs = ['/etc/syslog.conf', rsyslog] logs = [] if self.path_exists(rsyslog): with open(self.path_join(rsyslog), 'r', encoding='UTF-8') as conf: for line in conf.readlines(): if line.startswith('$IncludeConfig'): confs += glob.glob(line.split()[1]) for conf in confs: if not self.path_exists(self.path_join(conf)): continue config = self.path_join(conf) logs += self.do_regex_find_all(r"^\S+\s+(-?\/.*$)\s+", config) for i in logs: if i.startswith("-"): i = i[1:] if self.path_isfile(i): self.add_copy_spec(i) self.add_copy_spec([ "/etc/syslog.conf", "/etc/rsyslog.conf", "/etc/rsyslog.d", "/var/log/boot.log", "/var/log/installer", "/var/log/messages*", "/var/log/secure*", "/var/log/udev", "/var/log/dist-upgrade", "/var/log/auth.log", ]) self.add_cmd_output("journalctl --disk-usage") self.add_dir_listing('/var/log', recursive=True) # collect journal logs if: # - there is some data present, either persistent or runtime only # - systemd-journald service exists # otherwise fallback to collecting few well known logfiles directly journal = any(self.path_exists(self.path_join(p, "log/journal/")) for p in ["/var", "/run"]) if journal and self.is_service("systemd-journald"): self.add_journal(tags=['journal_full', 'journal_all'], priority=100) self.add_journal(boot="this", tags='journal_since_boot') self.add_journal(boot="last", tags='journal_last_boot') if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/journal/*", "/run/log/journal/*" ]) else: # If not using journal if not self.get_option("all_logs"): self.add_copy_spec([ "/var/log/syslog", "/var/log/syslog.1", "/var/log/syslog.2*", "/var/log/kern.log", "/var/log/kern.log.1", "/var/log/kern.log.2*", "/var/log/auth.log", "/var/log/auth.log.1", "/var/log/auth.log.2*", ]) else: self.add_copy_spec([ "/var/log/syslog*", "/var/log/kern.log*", "/var/log/auth.log*", ]) def postproc(self): self.do_path_regex_sub( r"/etc/rsyslog*", r"(ActionLibdbiPassword |pwd=)(.*)", r"\1[********]" ) class IndependentLogs(LogsBase, IndependentPlugin): """ This plugin will collect logs traditionally considered to be "system" logs, meaning those such as /var/log/messages, rsyslog, and journals that are not limited to unit-specific entries. Note that the --since option will apply to journal collections by this plugin as well as the typical application to log files. Most users can expect typical journal collections to include the "full" journal, as well as journals limited to this boot and the previous boot. """ plugin_name = "logs" profiles = ('system', 'hardware', 'storage') class CosLogs(LogsBase, CosPlugin): option_list = [ PluginOpt(name="log-days", default=3, desc="the number of days logs to collect") ] def setup(self): super().setup() if self.get_option("all_logs"): self.add_cmd_output("journalctl -o export") else: days = self.get_option("log-days", 3) self.add_journal(since=f"-{days}days") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/symcli.py0000664000175000017500000000673214660147624016451 0ustar arifarif# Copyright (C) 2008 EMC Corporation. Keith Kearnan # Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin from sos.utilities import is_executable class Symcli(Plugin, RedHatPlugin): short_desc = 'EMC Symcli' plugin_name = 'symcli' profiles = ('storage', 'hardware') def get_symcli_files(self): """ EMC Solutions Enabler SYMCLI specific information - files """ self.add_copy_spec([ "/var/symapi/db/symapi_db.bin", "/var/symapi/config/[a-z]*", "/var/symapi/log/[a-z]*" ]) def get_symcli_config(self): """ EMC Solutions Enabler SYMCLI specific information - Symmetrix/DMX - commands """ self.add_cmd_output([ "/usr/symcli/bin/symcli -def", "/usr/symcli/bin/symdg list", "/usr/symcli/bin/symdg -v list", "/usr/symcli/bin/symcg list", "/usr/symcli/bin/symcg -v list", "/usr/symcli/bin/symcfg list", "/usr/symcli/bin/symcfg -v list", "/usr/symcli/bin/symcfg -db", "/usr/symcli/bin/symcfg -semaphores list", "/usr/symcli/bin/symcfg -dir all -v list", "/usr/symcli/bin/symcfg -connections list", "/usr/symcli/bin/symcfg -app -v list", "/usr/symcli/bin/symcfg -fa all -port list", "/usr/symcli/bin/symcfg -ra all -port list", "/usr/symcli/bin/symcfg -sa all -port list", "/usr/symcli/bin/symcfg list -lock", "/usr/symcli/bin/symcfg list -lockn all", "/usr/symcli/bin/syminq", "/usr/symcli/bin/syminq -v", "/usr/symcli/bin/syminq -symmids", "/usr/symcli/bin/syminq hba -fibre", "/usr/symcli/bin/syminq hba -scsi", "/usr/symcli/bin/symhost show -config", "/usr/symcli/bin/stordaemon list", "/usr/symcli/bin/stordaemon -v list", "/usr/symcli/bin/sympd list", "/usr/symcli/bin/sympd list -vcm", "/usr/symcli/bin/symdev list", "/usr/symcli/bin/symdev -v list", "/usr/symcli/bin/symdev -rdfa list", "/usr/symcli/bin/symdev -rdfa -v list", "/usr/symcli/bin/symbcv list", "/usr/symcli/bin/symbcv -v list", "/usr/symcli/bin/symrdf list", "/usr/symcli/bin/symrdf -v list", "/usr/symcli/bin/symrdf -rdfa list", "/usr/symcli/bin/symrdf -rdfa -v list", "/usr/symcli/bin/symsnap list", "/usr/symcli/bin/symsnap list -savedevs", "/usr/symcli/bin/symclone list", "/usr/symcli/bin/symevent list", "/usr/symcli/bin/symmask list hba", "/usr/symcli/bin/symmask list logins", "/usr/symcli/bin/symmaskdb list database", "/usr/symcli/bin/symmaskdb -v list database" ]) def check_enabled(self): return is_executable("/usr/symcli/bin/symcli") def setup(self): self.get_symcli_files() self.get_symcli_config() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/tuned.py0000664000175000017500000000225514660147624016264 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Peter Portante # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Tuned(Plugin, RedHatPlugin): short_desc = 'Tuned system tuning daemon' packages = ('tuned',) profiles = ('system', 'performance') plugin_name = 'tuned' def setup(self): self.add_cmd_output("tuned-adm list", tags="tuned_adm") self.add_cmd_output([ "tuned-adm active", "tuned-adm recommend", "tuned-adm verify" ]) self.add_copy_spec("/etc/tuned.conf", tags="tuned_conf") self.add_copy_spec([ "/etc/tuned", "/etc/tune-profiles", "/usr/lib/tuned", "/var/log/tuned/tuned.log" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/tftpserver.py0000664000175000017500000000145314660147624017350 0ustar arifarif# Copyright (C) 2007 Shijoe George # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class TftpServer(Plugin, IndependentPlugin): short_desc = 'TFTP Server information' plugin_name = 'tftpserver' profiles = ('sysmgmt', 'network') files = ('/etc/xinetd.d/tftp',) packages = ('tftp-server',) def setup(self): self.add_dir_listing(['/tftpboot', '/sr/tftp'], recursive=True) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_masakari.py0000664000175000017500000000412414660147624021001 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class OpenStackMasakari(Plugin, UbuntuPlugin): short_desc = 'OpenStack Masakari' plugin_name = "openstack_masakari" profiles = ('openstack', 'openstack_controller') packages = ( 'masakari-engine', 'masakari-api', 'python3-masakari', ) services = ('masakari-engine', ) config_dir = "/etc/masakari" def setup(self): masakari_cmd = "masakari-manage --config-dir"\ f"{self.config_dir} db version" self.add_cmd_output( masakari_cmd, suggest_filename="masakari_db_version" ) self.add_copy_spec([ self.config_dir, ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/masakari/*", "/var/log/apache2/masakari*", ]) else: self.add_copy_spec([ "/var/log/masakari/*.log", "/var/log/apache2/masakari*.log", ]) self.add_file_tags({ f"{self.config_dir}/masakari.conf": "masakari_conf" }) def postproc(self): protect_keys = [".*password.*", "transport_url", "memcache_secret_key", "rabbit_password"] connection_keys = ["connection", "sql_connection"] join_con_keys = "|".join(connection_keys) self.do_path_regex_sub( f"{self.config_dir}/*", fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)", r"\1*********" ) self.do_path_regex_sub( f"{self.config_dir}/*", fr"(^\s*({join_con_keys})\s*=\s*(.*)://(\w*):)(.*)(@(.*))", r"\1*********\6" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/nis.py0000664000175000017500000000145414660147624015736 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Nis(Plugin, IndependentPlugin): short_desc = 'Network Information Service' plugin_name = 'nis' profiles = ('identity', 'services') files = ('/var/yp', '/etc/ypserv.conf') packages = ('ypserv',) def setup(self): self.add_copy_spec([ "/etc/yp*.conf", "/var/yp/*" ]) self.add_cmd_output("domainname") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/peripety.py0000664000175000017500000000227614660147624017011 0ustar arifarif# Copyright (C) 2018 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from re import match import glob from sos.report.plugins import Plugin, RedHatPlugin class Peripety(Plugin, RedHatPlugin): short_desc = 'Peripety Storage Event Monitor' plugin_name = 'peripety' packages = ('peripety',) services = ('peripetyd',) def setup(self): self.add_copy_spec('/etc/peripetyd.conf') forbid_reg = [ 'vd.*', 'sr.*', 'loop.*', 'ram.*' ] disks = filter(lambda x: not any(match(reg, x) for reg in forbid_reg), [d.split('/')[-1] for d in glob.glob('/sys/block/*')]) for disk in disks: self.add_cmd_output([ f"prpt info {disk}", f"prpt query --blk {disk}", ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rhui.py0000664000175000017500000000443114660147624016112 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Pavel Moravec # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Rhui(Plugin, RedHatPlugin): short_desc = 'Red Hat Update Infrastructure' plugin_name = "rhui" commands = ("rhui-manager", ) files = ("/etc/rhui/rhui-tools.conf", ) def setup(self): self.add_copy_spec([ "/etc/rhui/rhui-tools.conf", "/etc/rhui/registered_subscriptions.conf", "/etc/pki/rhui/*", "/var/log/rhui-subscription-sync.log", "/var/cache/rhui/*", "/root/.rhui/*", "/var/log/rhui/*", "/var/log/rhui-installer/*", ]) # skip collecting certificate keys self.add_forbidden_path("/etc/pki/rhui/**/*.key") # call rhui-manager commands with 1m timeout and # with an env. variable ensuring that "RHUI Username:" # even unanswered prompt gets collected self.add_cmd_output([ "rhui-manager status", "rhui-manager cert info", ], timeout=60, env={'PYTHONUNBUFFERED': '1'}) self.add_dir_listing('/var/lib/rhui/remote_share', recursive=True) def postproc(self): # hide rhui_manager_password value in (also rotated) answers file self.do_path_regex_sub( r"/root/\.rhui/answers.yaml.*", r"(\s*(rhui_manager|registry)_password\s*:)\s*(\S+)", r"\1********") # hide registry_password value in rhui-tools.conf self.do_path_regex_sub("/etc/rhui/rhui-tools.conf", r"(registry_password:)\s*(.+)", r"\1 ********") # obfuscate two cookies for login session for cookie in ["csrftoken", "sessionid"]: self.do_path_regex_sub( r"/root/\.rhui/.*/cookies.txt", fr"({cookie}\s+)(\S+)", r"\1********") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/puppet.py0000664000175000017500000000430714660147624016462 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, IndependentPlugin class Puppet(Plugin, IndependentPlugin): short_desc = 'Puppet service' plugin_name = 'puppet' profiles = ('services',) packages = ('puppet', 'puppet-common', 'puppet-server', 'puppetserver', 'puppetmaster', 'puppet-master') def setup(self): _hostname = self.exec_cmd('hostname')['output'] _hostname = _hostname.strip() self.add_copy_spec([ "/etc/puppet/*.conf", "/etc/puppet/rack/*", "/etc/puppet/manifests/*", "/etc/puppet/ssl/ca/inventory.txt", "/var/log/puppet/*.log*", "/etc/puppetlabs/puppet/*.conf", "/etc/puppetlabs/puppetserver/conf.d/*.conf", "/etc/puppetlabs/puppet/rack/*", "/etc/puppetlabs/puppet/manifests/*", "/etc/puppetlabs/puppet/ssl/ca/inventory.txt", "/var/log/puppetlabs/puppetserver/*.log*", "/var/lib/puppetlabs/puppet/ssl/ca/inventory.txt", "/var/lib/puppet/ssl/ca/inventory.txt", "/var/lib/puppet/ssl/certs/ca.pem", f"/etc/puppetlabs/puppet/ssl/certs/{_hostname}.pem", f"/var/lib/puppet/ssl/certs/{_hostname}.pem", ]) self.add_copy_spec("/etc/puppetlabs/puppet/ssl/certs/ca.pem", tags="puppet_ssl_cert_ca_pem") self.add_cmd_output([ 'facter', 'puppet --version', ]) self.add_dir_listing([ '/etc/puppet/modules', '/etc/puppetlabs/code/modules' ], recursive=True) def postproc(self): for device_conf in glob("/etc/puppet/device.conf*"): self.do_file_sub( device_conf, r"(.*url*.ssh://.*:).*(@.*)", r"\1***\2" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/lustre.py0000664000175000017500000000535114660147624016463 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.report.plugins import Plugin, RedHatPlugin class Lustre(Plugin, RedHatPlugin): short_desc = 'Lustre filesystem' plugin_name = 'lustre' profiles = ('storage', 'network', 'cluster', ) packages = ('lustre', 'lustre-client', ) def get_params(self, name, param_list): """Use lctl get_param to collect a selection of parameters into a file. """ self.add_cmd_output(f"lctl get_param {' '.join(param_list)}", suggest_filename=f"params-{name}", stderr=False) def setup(self): self.add_cmd_output([ "lctl debug_kernel", "lctl device_list", "lctl list_nids", "lctl route_list", "lnetctl net show -v 4" ]) # Grab almost everything output = self.exec_cmd("lctl list_param -R *")['output'] exclude = (".*@.*|.*dump_page_cache|peers|.*quota.*|osd-.*|.*osc.*|" "mgs.*|.*mgc_irstate|ldlm.*state|.*job_stats|.*exports.*") params = [item for item in output.splitlines() if not re.match(exclude, item)] self.get_params("all", params) self.get_params( "basic", ["version", "health_check", "debug"] ) # Client Specific self.add_cmd_output([ "lfs df", "lfs df -i" ]) self.get_params("osc_client", [ "osc.*.max_dirty_mb", "osc.*.max_pages_per_rpc", "osc.*.checksums", "osc.*.max_rpcs_in_flight" ]) # Server Specific self.get_params( "osd", ["osd-*.*.{mntdev,files*,kbytes*,blocksize,brw_stats}"] ) self.get_params("quota", ["osd-*.*.quota_slave.{info,limit_*,acct_*}"]) self.get_params("mgs", ["mgs.MGS.ir_timeout", "mgs.MGS.live.*"]) # mb_groups can be VERY large, and provide minimal debug usefulness self.add_forbidden_path("*/mb_groups") self.add_copy_spec([ "/sys/fs/ldiskfs", "/proc/fs/ldiskfs", ]) # Grab emergency ring buffer dumps and other largish info if self.get_option("all_logs"): self.add_copy_spec("/tmp/lustre-log.*") self.get_params("job-stats", ["*.*.job_stats"]) self.get_params("peers", ["peers"]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/aap_controller.py0000664000175000017500000000622514660147624020152 0ustar arifarif# Copyright (c) 2024 Pavel Moravec # Copyright (c) 2024 Lucas Benedito # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class AAPControllerPlugin(Plugin, RedHatPlugin): short_desc = 'AAP Automation Controller plugin' plugin_name = 'aap_controller' profiles = ('sysmgmt', 'ansible',) packages = ('automation-controller-venv-tower', 'automation-controller-server', 'automation-controller-ui', 'automation-controller') commands = ('awx-manage',) services = ('automation-controller',) def setup(self): self.add_copy_spec([ "/etc/tower/", "/etc/supervisord.conf", "/etc/supervisord.d/*", "/var/log/tower", "/var/log/nginx/automationcontroller.access.log*", "/var/log/nginx/automationcontroller.error.log*", "/var/log/supervisor", "/var/log/unattended-upgrades", ]) self.add_forbidden_path([ "/etc/tower/SECRET_KEY", "/etc/tower/*.key", "/etc/tower/*.cert", "/var/log/tower/profile", ]) self.add_cmd_output([ "awx-manage --version", "awx-manage list_instances", "awx-manage run_dispatcher --status", "awx-manage run_callback_receiver --status", "awx-manage check_license --data", "awx-manage run_wsbroadcast --status", "awx-manage run_wsrelay --status", "supervisorctl status", "/var/lib/awx/venv/awx/bin/pip freeze", "/var/lib/awx/venv/awx/bin/pip freeze -l", "/var/lib/awx/venv/ansible/bin/pip freeze", "/var/lib/awx/venv/ansible/bin/pip freeze -l", "umask -p", ]) self.add_dir_listing([ '/var/lib/awx', '/var/lib/awx/venv', '/etc/tower', ]) self.add_dir_listing('/var/lib/awx', tree=True) def postproc(self): # remove database password jreg = r"(\s*\'PASSWORD\'\s*:(\s))(?:\"){1,}(.+)(?:\"){1,}" repl = r"\1********" self.do_path_regex_sub("/etc/tower/conf.d/postgres.py", jreg, repl) # remove email password jreg = r"(EMAIL_HOST_PASSWORD\s*=)\'(.+)\'" repl = r"\1********" self.do_path_regex_sub("/etc/tower/settings.py", jreg, repl) # remove email password (if customized) jreg = r"(EMAIL_HOST_PASSWORD\s*=)\'(.+)\'" repl = r"\1********" self.do_path_regex_sub("/etc/tower/conf.d/custom.py", jreg, repl) # remove websocket secret jreg = r"(BROADCAST_WEBSOCKET_SECRET\s*=\s*)\"(.+)\"" repl = r"\1********" self.do_path_regex_sub("/etc/tower/conf.d/channels.py", jreg, repl) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/mongodb.py0000664000175000017500000000372114660147624016571 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class MongoDb(Plugin, DebianPlugin, UbuntuPlugin): short_desc = 'MongoDB document database' plugin_name = 'mongodb' profiles = ('services',) packages = ('mongodb-server',) var_puppet_gen = "/var/lib/config-data/puppet-generated/mongodb" files = ( '/etc/mongodb.conf', var_puppet_gen + '/etc/mongod.conf' ) def setup(self): self.add_copy_spec([ "/etc/mongodb.conf", self.var_puppet_gen + "/etc/", self.var_puppet_gen + "/etc/systemd/system/mongod.service.d/", "/var/log/mongodb/mongodb.log", "/var/lib/mongodb/mongodb.log*" ]) self.add_cmd_output("du -sh /var/lib/mongodb/") def postproc(self): for file in ["/etc/mongodb.conf", self.var_puppet_gen + "/etc/mongodb.conf"]: self.do_file_sub( file, r"(mms-token)\s*=\s*(.*)", r"\1 = ********" ) class RedHatMongoDb(MongoDb, RedHatPlugin): packages = ( 'mongodb-server', 'rh-mongodb32-mongodb-server', 'rh-mongodb34-mongodb-server', 'rh-mongodb36-mongodb-server' ) def setup(self): super().setup() self.add_copy_spec([ "/etc/sysconfig/mongodb", "/etc/rh-mongodb*-mongo*.conf", "/etc/opt/rh/rh-mongodb*/mongo*.conf", "/var/opt/rh/rh-mongodb*/log/mongodb/mongod.log" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/curtin.py0000664000175000017500000000337214660147624016452 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Curtin(Plugin, IndependentPlugin): short_desc = 'Curt Installer' plugin_name = 'curtin' profiles = ('boot',) files = ('/root/curtin-install-cfg.yaml', ) def setup(self): self.add_copy_spec([ '/root/curtin-install.log', '/root/curtin-install-cfg.yaml', '/etc/default/grub.d/50-curtin-settings.cfg', '/etc/apt/apt.conf.d/90curtin-aptproxy', '/etc/apt/sources.list.curtin.old', '/etc/cloud/cloud.cfg.d/50-curtin-networking.cfg', '/etc/cloud/cloud.cfg.d/curtin-preserve-sources.cfg', ]) def postproc(self): protect_keys = [ "oauth_consumer_key", "oauth_token_key", "token_key", "token_secret", "consumer_key", ] curtin_files = [ "/root/curtin-install-cfg.yaml", "/root/curtin-install.log", ] match_exp_multil = fr"({'|'.join(protect_keys)})\s*(:|=)(\S*\n.*?\\n)" match_exp = fr"({'|'.join(protect_keys)})\s*(:|=)\s*[a-zA-Z0-9]*" for file in curtin_files: self.do_file_sub( file, match_exp_multil, r"\1\2*********" ) self.do_file_sub( file, match_exp, r"\1\2*********" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/aap_eda.py0000664000175000017500000000401014660147624016506 0ustar arifarif# Copyright (c) 2023 Rudnei Bertol Jr # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class AAPEDAControllerPlugin(Plugin, RedHatPlugin): short_desc = 'AAP EDA Controller plugin' plugin_name = 'aap_eda' profiles = ('sysmgmt', 'ansible') packages = ('automation-eda-controller', 'automation-eda-controller-server') def setup(self): self.add_copy_spec([ "/etc/ansible-automation-platform/", "/var/log/ansible-automation-platform/eda/worker.log*", "/var/log/ansible-automation-platform/eda/scheduler.log*", "/var/log/ansible-automation-platform/eda/gunicorn.log*", "/var/log/ansible-automation-platform/eda/activation.log*", "/var/log/nginx/automationedacontroller.access.log*", "/var/log/nginx/automationedacontroller.error.log*", ]) self.add_forbidden_path([ "/etc/ansible-automation-platform/eda/SECRET_KEY", "/etc/ansible-automation-platform/eda/server.cert", "/etc/ansible-automation-platform/eda/server.key", ]) self.add_cmd_output("aap-eda-manage --version") self.add_dir_listing([ "/etc/ansible-automation-platform/", "/var/log/ansible-automation-platform/", ], recursive=True) self.add_cmd_output("su - eda -c 'export'", suggest_filename="eda_export") def postproc(self): self.do_path_regex_sub( "/etc/ansible-automation-platform/eda/environment", r"(EDA_SECRET_KEY|EDA_DB_PASSWORD)(\s*)(=|:)(\s*)(.*)", r'\1\2\3\4********') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/lstopo.py0000664000175000017500000000232014660147624016456 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin from sos.utilities import is_executable class Lstopo(Plugin, IndependentPlugin): short_desc = 'Machine topology information' plugin_name = "lstopo" profiles = ("system", "hardware") packages = ( "hwloc-libs", "libhwloc5", "hwloc", ) def setup(self): # binary depends on particular package, both require hwloc-libs one # hwloc-gui provides lstopo command # hwloc provides lstopo-no-graphics command if is_executable("lstopo"): cmd = "lstopo" else: cmd = "lstopo-no-graphics" self.add_cmd_output(f"{cmd} --whole-io --of console", suggest_filename="lstopo.txt") self.add_cmd_output(f"{cmd} --whole-io --of xml", suggest_filename="lstopo.xml") sos-4.8.0/sos/report/plugins/maas.py0000664000175000017500000001210314660147624016057 0ustar arifarif# Copyright (C) 2013 Adam Stokes # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import Plugin, UbuntuPlugin class MAAS(Plugin, UbuntuPlugin): short_desc = 'MAAS | Metal as a Service' plugin_name = 'maas' plugin_timeout = 1800 profiles = ('sysmgmt',) packages = ( 'maas', 'maas-region-api', 'maas-region-controller', 'maas-rack-controller', 'maas-agent', ) _services = ( 'maas-agent', 'maas-apiserver', 'maas-dhcpd', 'maas-dhcpd6', 'maas-http', 'maas-proxy', 'maas-rackd', 'maas-regiond', 'maas-syslog', 'maas-temporal', 'maas-temporal-worker', 'snap.maas.supervisor', 'snap.maas.pebble', ) def _get_machines_syslog(self, directory): if not self.path_exists(directory): return [] # Machine messages are collected with syslog and are stored under: # $template "{{log_dir}}/rsyslog/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%" # Collect only the most recent "%$YEAR%-%$MONTH%-%$DAY%" # for each "%HOSTNAME%". recent = [] for host_dir in self.listdir(directory): host_path = self.path_join(directory, host_dir) if not self.path_isdir(host_path): continue subdirs = [ self.path_join(host_path, d) for d in self.listdir(host_path) if self.path_isdir(host_path) ] if not subdirs: continue sorted_subdirs = sorted( subdirs, key=lambda d: os.stat(d).st_mtime, reverse=True ) all_logs = self.get_option("all_logs") since = self.get_option("since") if not all_logs and not since: recent.append(sorted_subdirs[0]) else: since = since.timestamp() if since else 0 recent.extend( [d for d in sorted_subdirs if os.stat(d).st_mtime >= since] ) return recent def _snap_collect(self): self.add_cmd_output([ 'snap info maas', 'maas status', ], snap_cmd=True) self.add_forbidden_path([ "/var/snap/maas/**/*.key", "/var/snap/maas/**/*.pem", "/var/snap/maas/**/secret", ]) self.add_copy_spec([ "/var/snap/maas/common/snap_mode", "/var/snap/maas/common/log/**/*.log", "/var/snap/maas/current/**/*.conf", "/var/snap/maas/current/**/*.yaml", "/var/snap/maas/current/bind", "/var/snap/maas/current/preseeds", "/var/snap/maas/current/supervisord/*.log", ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/snap/maas/common/log/**/*.log.*", "/var/snap/maas/current/supervisord/*.log.*", ]) self.add_copy_spec( self._get_machines_syslog( "/var/snap/maas/common/log/rsyslog" ) ) def _deb_collect(self): self.add_cmd_output([ "apt-cache policy maas maas-*", ]) self.add_forbidden_path([ "/var/lib/maas/**/*.key", "/var/lib/maas/**/*.pem", "/var/lib/maas/**/secret", "/etc/maas/**/*.key", "/etc/maas/**/*.pem", "/etc/maas/**/secret", ]) self.add_copy_spec([ "/etc/maas/**/*.conf", "/etc/maas/**/*.yaml", "/etc/maas/preseeds", "/var/lib/maas/**/*.conf", "/var/lib/maas/dhcp/*.leases", "/var/lib/maas/temporal", "/var/log/maas/**/*.log", ]) if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/maas/**/*.log.*", ]) self.add_copy_spec( self._get_machines_syslog( "/var/log/maas/rsyslog" ) ) def setup(self): for service in self._services: if self.is_service(service): self.add_service_status(service) if not self.get_option('all_logs'): since = self.get_option("since") or "-1days" self.add_journal(service, since=since) else: self.add_journal(service) if self.is_snap: self._snap_collect() else: self._deb_collect() def postproc(self): self.do_path_regex_sub( r"(.*)\.(conf|yaml|yml|toml)$", r"((?:.*secret|.*password|.*pass)(?::\s*|=\s*))(.*)", r"\1*****" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/processor.py0000664000175000017500000000473414660147624017170 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin, SoSPredicate from sos.policies.distros.ubuntu import UbuntuPolicy class Processor(Plugin, IndependentPlugin): short_desc = 'CPU information' plugin_name = 'processor' profiles = ('system', 'hardware', 'memory') files = ('/proc/cpuinfo',) packages = ('cpufreq-utils', 'cpuid') cpu_kmods = [] def setup(self): cpupath = '/sys/devices/system/cpu' self.add_file_tags({ f"{cpupath}/smt/control": 'cpu_smt_control', f"{cpupath}/smt/active": 'cpu_smt_active', f"{cpupath}/vulnerabilities/.*": 'cpu_vulns', f"{cpupath}/vulnerabilities/spectre_v2": 'cpu_vulns_spectre_v2', f"{cpupath}/vulnerabilities/meltdown": 'cpu_vulns_meltdown', f"{cpupath}/cpu.*/online": 'cpu_cores', f"{cpupath}/cpu/cpu0/cpufreq/cpuinfo_max_freq": 'cpuinfo_max_freq' }) self.add_copy_spec([ "/proc/cpuinfo", "/sys/class/cpuid", ]) # copy /sys/devices/system/cpu/cpuX with separately applied sizelimit # this is required for systems with tens/hundreds of CPUs where the # cumulative directory size exceeds 25MB or even 100MB. cdirs = self.listdir('/sys/devices/system/cpu') self.add_copy_spec([ self.path_join('/sys/devices/system/cpu', cdir) for cdir in cdirs ]) self.add_cmd_output([ "lscpu", "lscpu -ae", "cpufreq-info", "cpuid", "cpuid -r", ], cmd_as_tag=True) if (isinstance(self.policy, UbuntuPolicy) and self.policy.dist_version() >= 24.04): self.cpu_kmods = ['msr'] cpupower_pred = SoSPredicate(self, kmods=self.cpu_kmods) self.add_cmd_output([ "cpupower frequency-info", "cpupower info", "cpupower idle-info", "turbostat --debug sleep 10", ], cmd_as_tag=True, pred=cpupower_pred) if '86' in self.policy.get_arch(): self.add_cmd_output("x86info -a") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/ovirt_node.py0000664000175000017500000000316614660147624017317 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Lev Veyde # Copyright (C) 2018 Red Hat, Inc., # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class OvirtNode(Plugin, RedHatPlugin): short_desc = 'oVirt Node specific information' packages = ( 'imgbased', 'ovirt-node-ng-nodectl', ) plugin_name = 'ovirt_node' profiles = ('virt',) def setup(self): # Add log files self.add_copy_spec([ '/var/log/imgbased.log', # Required for node versions < 4.2 '/tmp/imgbased.log', ]) certificates = [ '/etc/pki/vdsm/certs/cacert.pem', '/etc/pki/vdsm/certs/vdsmcert.pem', '/etc/pki/vdsm/libvirt-spice/ca-cert.pem', '/etc/pki/vdsm/libvirt-spice/server-cert.pem', '/etc/pki/vdsm/libvirt-vnc/ca-cert.pem', '/etc/pki/vdsm/libvirt-vnc/server-cert.pem', ] # Collect runtime info self.add_cmd_output([ 'imgbase layout', 'nodectl --machine-readable check', 'nodectl info', ]) # Collect certificate info self.add_cmd_output([ f'openssl x509 -in {c} -text -noout' for c in certificates ]) # vim: expandtab tabstop=4 shiftwidth=4 sos-4.8.0/sos/report/plugins/acpid.py0000664000175000017500000000162514660147624016225 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Acpid(Plugin): short_desc = 'ACPI daemon information' plugin_name = "acpid" profiles = ('hardware',) packages = ('acpid',) class RedHatAcpid(Acpid, RedHatPlugin): def setup(self): self.add_copy_spec([ "/var/log/acpid*", "/etc/acpi/events/power.conf"]) class DebianAcpid(Acpid, DebianPlugin, UbuntuPlugin): def setup(self): self.add_copy_spec([ "/etc/acpi/events/powerbtn*"]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/os_net_config.py0000664000175000017500000000134714660147624017762 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class OsNetConfig(Plugin, IndependentPlugin): short_desc = 'OpenStack Net Config' plugin_name = "os_net_config" profiles = ('openstack',) packages = ('os-net-config',) def setup(self): self.add_copy_spec("/etc/os-net-config") self.add_copy_spec("/var/lib/os-net-config") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/hyperv.py0000664000175000017500000000156614660147624016466 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Hyperv(Plugin, IndependentPlugin): """Hyper-V client information""" short_desc = 'Microsoft Hyper-V client information' plugin_name = "hyperv" files = ('/sys/bus/vmbus/',) def setup(self): self.add_copy_spec([ "/sys/bus/vmbus/drivers/", # copy devices/*/* instead of devices/ to follow link files "/sys/bus/vmbus/devices/*/*" ]) self.add_cmd_output("lsvmbus -vv") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/haproxy.py0000664000175000017500000000464314660147624016642 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from re import match from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse class HAProxy(Plugin, RedHatPlugin, DebianPlugin): short_desc = 'HAProxy load balancer' plugin_name = 'haproxy' profiles = ('webserver',) packages = ('haproxy',) var_puppet_gen = '/var/lib/config-data/puppet-generated/haproxy' files = (var_puppet_gen, ) def setup(self): self.add_copy_spec([ "/etc/haproxy/haproxy.cfg", self.var_puppet_gen + "/etc/haproxy/haproxy.cfg" ]) self.add_copy_spec("/etc/haproxy/conf.d/*") self.add_cmd_output("haproxy -f /etc/haproxy/haproxy.cfg -c") self.add_copy_spec("/var/log/haproxy.log") self.add_service_status('haproxy') self.add_journal(units='haproxy') # collect haproxy overview - curl to IP address taken from haproxy.cfg # as 2nd word on line below "haproxy.stats" # so parse haproxy.cfg until "haproxy.stats" read, and take 2nd word # from the next line matched = None provision_ip = None try: _haproxy_file = "/etc/haproxy/haproxy.cfg" with open(_haproxy_file, 'r', encoding='UTF-8') as hfile: for line in hfile.read().splitlines(): if matched: provision_ip = line.split()[1] break matched = match(r".*haproxy\.stats.*", line) except IOError: # fallback when the cfg file is not accessible pass if not provision_ip: return # check if provision_ip contains port - if not, add default ":1993" if urlparse("http://"+provision_ip).port is None: provision_ip = provision_ip + ":1993" self.add_cmd_output("curl http://"+provision_ip+r"/\;csv", suggest_filename="haproxy_overview.txt") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/gluster_block.py0000664000175000017500000000210614660147624017777 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob from sos.report.plugins import Plugin, RedHatPlugin class GlusterBlock(Plugin, RedHatPlugin): short_desc = 'Gluster Block' plugin_name = 'gluster_block' profiles = ('storage',) packages = ("gluster-block",) files = ("/usr/sbin/gluster-block",) def setup(self): # collect logs - apply log_size for any individual file # all_logs takes precedence over logsize if not self.get_option("all_logs"): limit = self.get_option("log_size") else: limit = 0 if limit: for file in glob.glob("/var/log/gluster-block/*.log"): self.add_copy_spec(file, limit) else: self.add_copy_spec("/var/log/gluster-block") sos-4.8.0/sos/report/plugins/xinetd.py0000664000175000017500000000150514660147624016435 0ustar arifarif# Copyright (C) 2007 Red Hat, Inc., Eugene Teo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Xinetd(Plugin, IndependentPlugin): short_desc = 'xinetd information' plugin_name = 'xinetd' profiles = ('services', 'network', 'boot') files = ('/etc/xinetd.conf',) packages = ('xinetd',) def setup(self): self.add_copy_spec([ "/etc/xinetd.conf", "/etc/xinetd.d" ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/pam.py0000664000175000017500000000256614660147624015727 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin, UbuntuPlugin class Pam(Plugin): short_desc = 'Pluggable Authentication Modules' plugin_name = "pam" profiles = ('security', 'identity', 'system') verify_packages = ('pam_.*',) security_libs = "" def setup(self): self.add_file_tags({ '/etc/pam.d/password-auth': 'password_auth', '/etc/security/limits.*.conf': 'limits_conf' }) self.add_copy_spec([ "/etc/pam.d", "/etc/security", '/etc/authselect/authselect.conf', ]) self.add_cmd_output([ "pam_tally2", "faillock" ]) self.add_dir_listing(self.security_libs) class RedHatPam(Pam, RedHatPlugin): security_libs = "/lib*/security" def setup(self): super().setup() self.add_cmd_output(["authselect current"]) class DebianPam(Pam, DebianPlugin, UbuntuPlugin): security_libs = "/lib/x86_64-linux-gnu/security" # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/kernelrt.py0000664000175000017500000000325014660147624016767 0ustar arifarif# Copyright 2012 Red Hat Inc. # Guy Streeter # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, SoSPredicate class KernelRT(Plugin, RedHatPlugin): short_desc = 'Realtime kernel variant' plugin_name = 'kernelrt' profiles = ('system', 'hardware', 'kernel', 'mrg') # this file exists only when the realtime kernel is booted # this plugin will not be called is this file does not exist files = ('/sys/kernel/realtime',) def setup(self): clocksource_path = '/sys/devices/system/clocksource/clocksource0/' self.add_copy_spec([ '/etc/rtgroups', '/proc/sys/kernel/sched_rt_period_us', '/proc/sys/kernel/sched_rt_runtime_us', '/sys/kernel/realtime', clocksource_path + 'available_clocksource', clocksource_path + 'current_clocksource' ]) # note: rhbz#1059685 'tuna - NameError: global name 'cgroups' is not # defined this command throws an exception on versions prior to # 0.10.4-5. cout = {'cmd': 'tuna --help', 'output': '-P'} option_present = self.test_predicate( self, pred=SoSPredicate(self, cmd_outputs=cout) ) self.add_cmd_output( f"tuna {'-CP' if option_present else 'show_threads -C'}" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/seagate_ses.py0000664000175000017500000000414714660147624017432 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class SeagateSES(Plugin, IndependentPlugin): """The seagate_ses plugin collect information about all connected seagate storage shelves. It captures Controller status information, ID, controllers' VPD information, Environmental zone, Drive, PHY details, Cooling Module and PSU information. """ short_desc = 'Seagate SES status' plugin_name = 'seagate_ses' plugin_timeout = 600 profiles = ('system', 'storage', 'hardware',) packages = ('fwdownloader_megaraid',) def setup(self): res = self.collect_cmd_output('fwdownloader -ses') # Finding actual SES devices and ignoring 0th element # as it does not contain any device information op_lst = [] if res['status'] == 0: op_lst = res['output'].split("SES Device")[1:] devices = [ i for i in range(len(op_lst)) if "Vendor ID: SEAGATE" in op_lst[i] ] cmd = 'getstatus -d' subcmds = [ 'ddump_canmgr', 'ddump_cblmgr', 'ddump_drvmgr', 'dumpdrives', 'ddump_phycounters', 'ddump_pwrmgr', 'ddump_envctrl', 'envctrl_fan', 'envctrl_zone', 'fwstatus', 'getboardid', 'getvpd', 'report_faults', 'ver', 'logdump', 'phydump', 'ses_reportpage 2', ] for devid in devices: self.add_cmd_output([ f"{cmd} {devid} -CLI {subcmd}" for subcmd in subcmds ]) self.add_cmd_output([ f"{cmd} {devid} -cli {subcmd}" for subcmd in subcmds ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/openstack_designate.py0000664000175000017500000000714614660147624021163 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, UbuntuPlugin class OpenStackDesignate(Plugin): short_desc = 'Openstack Designate' plugin_name = "openstack_designate" profiles = ('openstack', 'openstack_controller') var_puppet_gen = "/var/lib/config-data/puppet-generated/designate" var_ansible_gen = "/var/lib/config-data/ansible-generated" def setup(self): # collect current pool config self.add_cmd_output( "designate-manage pool generate_file --file /dev/stdout", container=self.get_container_by_name(".*designate_central"), suggest_filename="openstack_designate_current_pools.yaml" ) # configs self.add_copy_spec([ "/etc/designate/*", self.var_puppet_gen + "/etc/designate/designate.conf", self.var_puppet_gen + "/etc/designate/pools.yaml", self.var_ansible_gen + "/designate/etc/designate/named.conf", self.var_ansible_gen + "/designate/etc/designate/named/*", self.var_ansible_gen + "/unbound/*" ]) # logs if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/designate/*", "/var/log/containers/designate/*", "/var/log/containers/designate-bind/*", "/var/log/containers/unbound/*" ]) else: self.add_copy_spec([ "/var/log/designate/*.log", "/var/log/containers/designate/*.log", "/var/log/containers/designate-bind/*.log", "/var/log/containers/unbound/*.log" ]) subcmds = [ 'dns service list', 'dns quota list', 'ptr record list', 'tld list', 'tsigkey list --column name --column algorithm --column scope', 'zone blacklist list', 'zone export list', 'zone import list', 'zone list', 'zone transfer accept list', 'zone transfer request list' ] # commands self.add_cmd_output([ f'openstack {sub} --all-projects' for sub in subcmds ]) # get recordsets for each zone cmd = "openstack zone list -f value -c id" ret = self.exec_cmd(cmd) if ret['status'] == 0: for zone in ret['output'].splitlines(): zone = zone.split()[0] self.add_cmd_output( f"openstack recordset list --all-projects {zone}", subdir='recordset') def postproc(self): protect_keys = [ "password", "connection", "transport_url", "admin_password", "ssl_key_password", "ssl_client_key_password", "memcache_secret_key" ] regexp = fr"(^\s*({'|'.join(protect_keys)})\s*=\s*)(.*)" self.do_path_regex_sub("/etc/designate/*", regexp, r"\1*********") self.do_path_regex_sub( self.var_puppet_gen + "/etc/designate/*", regexp, r"\1*********" ) class RedHatdesignate(OpenStackDesignate, RedHatPlugin): packages = ('openstack-selinux',) class Ubuntudesignate(OpenStackDesignate, UbuntuPlugin): packages = ('designate-common',) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/vhostmd.py0000664000175000017500000000333214660147624016626 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Vhostmd(Plugin, RedHatPlugin): short_desc = 'vhostmd virtualization metrics collection' plugin_name = 'vhostmd' profiles = ('sap', 'virt', 'system') packages = ('virt-what',) def setup(self): vwhat = self.collect_cmd_output("virt-what")['output'].splitlines() if not vwhat: return if "vmware" in vwhat or "kvm" in vwhat or "xen" in vwhat: if self.is_installed("vm-dump-metrics"): # if vm-dump-metrics is installed use it self.add_cmd_output("vm-dump-metrics", suggest_filename="virt_metrics") else: # otherwise use the raw vhostmd disk presented (256k size) sysblock = "/sys/block" if not self.path_isdir(sysblock): return for disk in self.listdir(sysblock): if "256K" in disk: dev = disk.split()[0] ret = self.exec_cmd(f"dd if=/dev/{dev} bs=25 count=1") if 'metric' in ret['output']: self.add_cmd_output( f"dd if=/dev/{dev} bs=256k count=1", suggest_filename="virt_metrics" ) # vim: et ts=4 sw=4 sos-4.8.0/sos/report/plugins/scsi.py0000664000175000017500000000463214660147624016107 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from glob import glob from sos.report.plugins import Plugin, IndependentPlugin class Scsi(Plugin, IndependentPlugin): """ Collects various information about the SCSI devices install on the host system. This plugin will capture a large amount of data from the /sys filesystem, as well as several different invocations of the `lsscsi` command. Additionally, several `sg_persist` commands will be collected for each SCSI device identified by sos. Note that in most cases these commands are provided by the `sg3_utils` package which may not be present by default. """ short_desc = 'SCSI devices' plugin_name = 'scsi' profiles = ('storage', 'hardware') def setup(self): self.add_copy_spec([ "/proc/scsi", "/etc/stinit.def", "/sys/bus/scsi", "/sys/class/scsi_host", "/sys/class/scsi_disk", "/sys/class/scsi_device", "/sys/class/scsi_generic" ]) scsi_types = ["enclosu"] result = self.collect_cmd_output('lsscsi -g') if result['status'] == 0: for line in result['output'].splitlines(): if line.split()[1] in scsi_types: devsg = line.split()[-1] self.add_cmd_output(f"sg_ses -p2 -b1 {devsg}") self.add_cmd_output("lsscsi -i", suggest_filename="lsscsi", tags="lsscsi") self.add_cmd_output([ "sg_map -x", "lspath", "lsmap -all", "lsnports", "lsscsi -H", "lsscsi -d", "lsscsi -s", "lsscsi -L", "lsscsi -iw", ]) scsi_hosts = glob("/sys/class/scsi_host/*") self.add_device_cmd("udevadm info -a %(dev)s", devices=scsi_hosts) self.add_device_cmd([ "sg_persist --in -k -d %(dev)s", "sg_persist --in -r -d %(dev)s", "sg_persist --in -s -d %(dev)s", "sg_inq %(dev)s" ], devices='block', whitelist=['sd.*']) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/rpm.py0000664000175000017500000000453414660147624015745 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Rpm(Plugin, RedHatPlugin): short_desc = 'RPM Package Manager' plugin_name = 'rpm' profiles = ('system', 'packagemanager') option_list = [ PluginOpt('rpmq', default=True, desc='query package information with rpm -q'), PluginOpt('rpmva', default=False, desc='verify all packages'), PluginOpt('rpmdb', default=False, desc='collect /var/lib/rpm') ] verify_packages = ('rpm',) def setup(self): self.add_copy_spec("/var/log/rpmpkgs") self.add_dir_listing('/var/lib/rpm', recursive=True) if self.get_option("rpmq"): rpmq = "rpm --nodigest -qa --qf=%s" # basic installed-rpms nvra = '"%-59{NVRA} %{INSTALLTIME:date}\n"' irpms = f"sh -c '{rpmq} | sort -V'" % nvra self.add_cmd_output(irpms, root_symlink='installed-rpms', tags='installed_rpms') # extended package data extpd = ( '"%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\\t' '%{INSTALLTIME:date}\\t%{INSTALLTIME}\\t' '%{VENDOR}\\t%{BUILDHOST}\\t' '%{SIGPGP}\\t%{SIGPGP:pgpsig}\\n"' ) self.add_cmd_output(rpmq % extpd, suggest_filename='package-data', tags=['installed_rpms', 'package_data']) if self.get_option("rpmva"): self.plugin_timeout = 1000 self.add_cmd_output("rpm -Va", root_symlink="rpm-Va", timeout=900, priority=100, tags=['rpm_va', 'rpm_V', 'rpm_v', 'rpm_V_packages']) if self.get_option("rpmdb"): self.add_cmd_output("lsof +D /var/lib/rpm", suggest_filename='lsof_D_var_lib_rpm') self.add_copy_spec("/var/lib/rpm") self.add_cmd_output("rpm --showrc") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/virtwho.py0000664000175000017500000000173114660147624016645 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class VirtWho(Plugin, RedHatPlugin): short_desc = 'Virt-Who agent' plugin_name = 'virtwho' profiles = ('virt', 'system') packages = ('virt-who',) def setup(self): self.add_copy_spec(["/etc/virt-who.d/*", "/etc/virt-who.conf"]) self.add_cmd_output("virt-who -dop") def postproc(self): # the regexp path catches both /etc/virt-who.d/ and /etc/virt-who.conf self.do_path_regex_sub(r"\/etc\/virt-who\.", r"(password=)(\S*)", r"\1********") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/redis.py0000664000175000017500000000312114660147624016244 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc., Abhijeet Kasurde # Copyright (C) 2017 Red Hat, Inc., Martin Schuppert # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Redis(Plugin, IndependentPlugin): short_desc = 'Redis, in-memory data structure store' plugin_name = 'redis' profiles = ('services',) packages = ('redis',) var_puppet_gen = "/var/lib/config-data/puppet-generated/redis" def setup(self): self.add_copy_spec([ "/etc/redis.conf", self.var_puppet_gen + "/etc/redis*", self.var_puppet_gen + "/etc/redis/", self.var_puppet_gen + "/etc/security/limits.d/" ]) self.add_cmd_output("redis-cli info") if self.get_option("all_logs"): self.add_copy_spec([ "/var/log/redis/redis.log*", ]) else: self.add_copy_spec([ "/var/log/redis/redis.log", ]) def postproc(self): for path in ["/etc/", self.var_puppet_gen + "/etc/"]: self.do_file_sub( path + "redis.conf", r"(masterauth|requirepass)\s.*", r"\1 ********" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/qt.py0000664000175000017500000000145114660147624015566 0ustar arifarif# Copyright (C) 2019 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Qt(Plugin, RedHatPlugin): short_desc = 'QT widget toolkit' plugin_name = 'qt' packages = ('qt', ) def setup(self): self.add_env_var([ 'QT_IM_MODULE', 'QTDIR', 'QTLIB', 'QT_PLUGIN_PATH', 'IMSETTINGS_MODULE' ]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/libreswan.py0000664000175000017500000000474614660147624017142 0ustar arifarif# Copyright (C) 2007 Sadique Puthen # Copyright (C) 2019 Red Hat Inc., Stepan Broz # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import (Plugin, IndependentPlugin, SoSPredicate, PluginOpt) class Libreswan(Plugin, IndependentPlugin): short_desc = 'Libreswan IPsec' plugin_name = 'libreswan' profiles = ('network', 'security', 'openshift') option_list = [ PluginOpt('ipsec-barf', default=False, desc='collect ipsec barf output') ] files = ('/etc/ipsec.conf',) packages = ('libreswan', 'openswan') def setup(self): self.add_copy_spec([ "/etc/ipsec.conf", "/etc/ipsec.d", "/proc/net/xfrm_stat" ]) # although this is 'verification' it's normally a very quick # operation so is not conditional on --verify self.add_cmd_output([ 'ipsec verify', 'ipsec whack --status', 'ipsec whack --listall', 'certutil -L -d sql:/etc/ipsec.d' ]) # may load xfrm kmods xfrm_pred = SoSPredicate(self, kmods=['xfrm_user', 'xfrm_algo'], required={'kmods': 'all'}) self.add_cmd_output([ 'ip xfrm policy', 'ip xfrm state' ], pred=xfrm_pred) if self.get_option("ipsec-barf"): self.add_cmd_output("ipsec barf") self.add_forbidden_path([ '/etc/ipsec.secrets', '/etc/ipsec.secrets.d', '/etc/ipsec.d/*.db', '/etc/ipsec.d/*.secrets' ]) def postproc(self): # Remove any sensitive data. # "ip xfrm state" output contains encryption or authentication private # keys: xfrm_state_regexp = r'(aead|auth|auth-trunc|enc)' \ r'(\s.*\s)(0x[0-9a-f]+)' self.do_cmd_output_sub("state", xfrm_state_regexp, r"\1\2********") if self.get_option("ipsec-barf"): self.do_cmd_output_sub("barf", xfrm_state_regexp, r"\1\2********") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/saltmaster.py0000664000175000017500000000423014660147624017317 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import glob import yaml from sos.report.plugins import Plugin, IndependentPlugin class SaltMaster(Plugin, IndependentPlugin): short_desc = 'Salt Master' plugin_name = 'saltmaster' profiles = ('sysmgmt',) packages = ('salt-master', 'salt-api',) def setup(self): if self.get_option("all_logs"): self.add_copy_spec("/var/log/salt") else: self.add_copy_spec("/var/log/salt/master") self.add_copy_spec("/etc/salt") self.add_forbidden_path("/etc/salt/pki/*/*.pem") self.add_pillar_roots() self.add_cmd_output([ "salt-master --version", "systemctl --full status salt-master", "systemctl --full status salt-api", "salt-key --list all", "salt-run jobs.list_jobs --out=yaml", "salt-run manage.list_state --out=yaml", "salt-run manage.list_not_state --out=yaml", "salt-run manage.joined --out=yaml", ], timeout=30) def add_pillar_roots(self): """ Collect pilliar_roots of all salt configs """ cfgs = glob.glob("/etc/salt/master.d/*conf") main_cfg = "/etc/salt/master" if self.path_exists(main_cfg): cfgs.append(main_cfg) all_pillar_roots = [] for cfg in cfgs: with open(cfg, "r", encoding='UTF-8') as file: cfg_pillar_roots = ( yaml.safe_load(file).get("pillar_roots", {}). get("base", []) ) all_pillar_roots.extend(cfg_pillar_roots) self.add_copy_spec(all_pillar_roots) def postproc(self): regexp = r'(^\s+.*(pass|secret|(? # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Mpt(Plugin, IndependentPlugin): short_desc = 'LSI Message Passing Technology' files = ('/proc/mpt',) profiles = ('storage', ) plugin_name = 'mpt' def setup(self): self.add_copy_spec("/proc/mpt") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/distupgrade.py0000664000175000017500000000300614660147624017453 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class DistUpgrade(Plugin): short_desc = 'Distribution upgrade data' plugin_name = "distupgrade" profiles = ('system', 'sysmgmt') class RedHatDistUpgrade(DistUpgrade, RedHatPlugin): packages = ( 'preupgrade-assistant', 'preupgrade-assistant-ui', 'preupgrade-assistant-el6toel7', 'redhat-upgrade-tool' ) files = ( "/var/log/upgrade.log", "/var/log/redhat_update_tool.log", "/root/preupgrade/all-xccdf*", "/root/preupgrade/kickstart" ) def postproc(self): self.do_file_sub( "/root/preupgrade/kickstart/anaconda-ks.cfg", r"(useradd --password) (.*)", r"\1 ********" ) self.do_file_sub( "/root/preupgrade/kickstart/anaconda-ks.cfg", r"(\s*rootpw\s*).*", r"\1********" ) self.do_file_sub( "/root/preupgrade/kickstart/untrackeduser", r"\/home\/.*", r"/home/******** path redacted ********" ) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/memory.py0000664000175000017500000000271514660147624016456 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, IndependentPlugin class Memory(Plugin, IndependentPlugin): short_desc = 'Memory configuration and use' plugin_name = 'memory' profiles = ('system', 'hardware', 'memory') def setup(self): self.add_copy_spec([ "/proc/pci", "/proc/meminfo", "/proc/vmstat", "/proc/swaps", "/proc/slabinfo", "/proc/pagetypeinfo", "/proc/vmallocinfo", "/sys/kernel/mm/ksm", "/sys/kernel/mm/transparent_hugepage/enabled", "/sys/kernel/mm/hugepages", "/sys/kernel/mm/lru_gen/enabled", "/sys/kernel/mm/lru_gen/min_ttl_ms", ]) self.add_cmd_output("free", root_symlink="free") self.add_cmd_output([ "free -m", "swapon --bytes --show", "swapon --summary --verbose", "lsmem -a -o RANGE,SIZE,STATE,REMOVABLE,ZONES,NODE,BLOCK" ]) # slabtop -o will hang if not handed a tty via stdin self.add_cmd_output("slabtop -o", foreground=True) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/jars.py0000664000175000017500000001262014660147624016101 0ustar arifarif# Copyright (C) 2016 Red Hat, Inc., Michal Srb # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import hashlib import json import os import re import zipfile from functools import partial from sos.report.plugins import Plugin, RedHatPlugin, PluginOpt class Jars(Plugin, RedHatPlugin): short_desc = 'Collect information about available Java archives' plugin_name = "jars" profiles = ("java",) option_list = [ PluginOpt('append-locations', default="", val_type=str, desc='colon-delimited list of additional JAR paths'), PluginOpt('all-known-locations', default=False, desc='scan all known paths') ] # There is no standard location for JAR files and scanning # the whole filesystem could be very slow. Therefore we only # scan directories in which JARs can be typically found. jar_locations = ( "/usr/share/java", # common location for JARs "/usr/lib/java" # common location for JARs containing native code ) # Following paths can be optionally scanned as well. Note the scan can take # *very* long time. extra_jar_locations = ( "/opt", # location for 3rd party software "/usr/local", # used by sysadmins when installing SW locally "/var/lib" # Java services commonly explode WARs there ) def setup(self): results = {"jars": []} jar_paths = [] locations = list(Jars.jar_locations) if self.get_option("all-known-locations"): locations += list(Jars.extra_jar_locations) # append also user-defined locations, if any user_locations = self.get_option("append-locations") if user_locations: locations += user_locations.split(":") # find all JARs in given locations for location in locations: for dirpath, _, filenames in os.walk(location): for filename in filenames: path = self.path_join(dirpath, filename) if self.is_jar(path): jar_paths.append(path) # try to extract information about found JARs for jar_path in jar_paths: maven_id = self.get_maven_id(jar_path) jar_id = self.get_jar_id(jar_path) if maven_id or jar_id: record = {"path": jar_path, "sha1": jar_id, "maven_id": maven_id } results["jars"].append(record) results_str = json.dumps(results, indent=4, separators=(",", ": ")) self.add_string_as_file(results_str, "jars.json", plug_dir=True) def is_jar(self, path): """Check whether given file is a JAR file. JARs are ZIP files which usually include a manifest at the canonical location 'META-INF/MANIFEST.MF'. """ if os.path.isfile(path) and zipfile.is_zipfile(path): try: with zipfile.ZipFile(path) as file: if "META-INF/MANIFEST.MF" in file.namelist(): return True except (IOError, zipfile.BadZipfile) as err: self._log_info( f"Could not determine if {path} is a JAR: {err}" ) return False def get_maven_id(self, jar_path): """Extract Maven coordinates from a given JAR file, if possible. JARs build by Maven (most popular Java build system) contain 'pom.properties' file. We can extract Maven coordinates from there. """ props = {} try: with zipfile.ZipFile(jar_path) as file: rgx = re.compile("META-INF/maven/[^/]+/[^/]+/pom.properties$") result = [x for x in file.namelist() if rgx.match(x)] if len(result) != 1: return None with file.open(result[0]) as props_f: for line in props_f.readlines(): line = line.strip() if not line.startswith(b"#"): try: (key, value) = line.split(b"=") key = key.decode('utf8').strip() value = value.decode('utf8').strip() props[key] = value except ValueError: return None except IOError as err: self._log_info( f"Could not extract Maven coordinates from {jar_path}: {err}" ) return props def get_jar_id(self, jar_path): """Compute JAR id. Returns sha1 hash of a given JAR file. """ jar_id = "" try: with open(jar_path, mode="rb") as file: digest = hashlib.sha1() for buf in iter(partial(file.read, 4096), b''): digest.update(buf) jar_id = digest.hexdigest() except IOError as err: self._log_info(f"Could not compute JAR id for {jar_path}: {err}") return jar_id sos-4.8.0/sos/report/plugins/vdo.py0000664000175000017500000000171614660147624015736 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, RedHatPlugin class Vdo(Plugin, RedHatPlugin): short_desc = 'Virtual Data Optimizer' plugin_name = 'vdo' profiles = ('storage',) packages = ('vdo',) files = ( '/sys/kvdo', '/sys/uds', '/etc/vdoconf.yml', '/etc/vdoconf.xml' ) def setup(self): self.add_copy_spec(self.files) vdos = self.collect_cmd_output('vdo list --all') for vdo in vdos['output'].splitlines(): self.add_cmd_output(f"vdo status -n {vdo}") self.add_cmd_output('vdostats --human-readable') # vim set et ts=4 sw=4 : sos-4.8.0/sos/report/plugins/unity.py0000664000175000017500000000127714660147624016320 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import Plugin, UbuntuPlugin class Unity(Plugin, UbuntuPlugin): short_desc = 'Unity' plugin_name = 'unity' profiles = ('hardware', 'desktop') packages = ( 'nux-tools', 'unity' ) def setup(self): self.add_cmd_output("/usr/lib/nux/unity_support_test -p") # vim: set et ts=4 sw=4 : sos-4.8.0/sos/report/reporting.py0000664000175000017500000001606514660147624015501 0ustar arifarif# Copyright (C) 2014 Red Hat, Inc., # Bryn M. Reeves # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. """ This provides a restricted tag language to define the sos report index/report """ try: import json except ImportError: import simplejson as json class Node: data = {} def __str__(self): return json.dumps(self.data) # pylint: disable=unused-argument def can_add(self, node): return False class Leaf(Node): """Marker class that can be added to a Section node""" class Report(Node): """The root element of a report. This is a container for sections.""" def __init__(self): self.data = {} def can_add(self, node): return isinstance(node, Section) def add(self, *nodes): for node in nodes: if self.can_add(node): self.data[node.name] = node.data def _decode(s): """returns a string text for a given unicode/str input""" return (s if isinstance(s, str) else s.decode('utf8', 'ignore')) class Section(Node): """A section is a container for leaf elements. Sections may be nested inside of Report objects only.""" def __init__(self, name): self.name = _decode(name) self.data = {} def can_add(self, node): return isinstance(node, Leaf) def add(self, *nodes): for node in nodes: if self.can_add(node): self.data.setdefault(node.ADDS_TO, []).append(node.data) class Command(Leaf): ADDS_TO = "commands" def __init__(self, name, return_code, href): self.data = {"name": _decode(name), "return_code": return_code, "href": _decode(href)} class CopiedFile(Leaf): ADDS_TO = "copied_files" def __init__(self, name, href): self.data = {"name": _decode(name), "href": _decode(href)} class CreatedFile(Leaf): ADDS_TO = "created_files" def __init__(self, name, href): self.data = {"name": _decode(name), "href": _decode(href)} class Alert(Leaf): ADDS_TO = "alerts" def __init__(self, content): self.data = _decode(content) class Note(Leaf): ADDS_TO = "notes" def __init__(self, content): self.data = _decode(content) def ends_bs(string): """ Return True if 'string' ends with a backslash, and False otherwise. Define this as a named function for no other reason than that pep8 now forbids binding of a lambda expression to a name: 'E731 do not assign a lambda expression, use a def' """ return string.endswith('\\') class PlainTextReport: """Will generate a plain text report from a top_level Report object""" HEADER = "" FOOTER = "" LEAF = " * %(name)s" ALERT = " ! %s" NOTE = " * %s" PLUGLISTHEADER = "Loaded Plugins:" PLUGLISTITEM = " {name}" PLUGLISTSEP = "\n" PLUGLISTMAXITEMS = 5 PLUGLISTFOOTER = "" PLUGINFORMAT = "{name}" PLUGDIVIDER = "=" * 72 subsections = ( (Command, LEAF, "- commands executed:", ""), (CopiedFile, LEAF, "- files copied:", ""), (CreatedFile, LEAF, "- files created:", ""), (Alert, ALERT, "- alerts:", ""), (Note, NOTE, "- notes:", ""), ) line_buf = [] def __init__(self, report_node): self.report_data = sorted(dict.items(report_node.data)) def unicode(self): self.line_buf = line_buf = [] if len(self.HEADER) > 0: line_buf.append(self.HEADER) # generate section/plugin list, split long list to multiple lines line_buf.append(self.PLUGLISTHEADER) line = "" i = 0 plugcount = len(self.report_data) for section_name, _ in self.report_data: line += f" {section_name}" i += 1 if (i % self.PLUGLISTMAXITEMS == 0) and (i < plugcount): line += self.PLUGLISTSEP line += self.PLUGLISTFOOTER line_buf.append(line) for section_name, section_contents in self.report_data: line_buf.append(self.PLUGDIVIDER) line_buf.append(f"{section_name}") for type_, format_, header, footer in self.subsections: self.process_subsection(section_contents, type_.ADDS_TO, header, format_, footer) if len(self.FOOTER) > 0: line_buf.append(self.FOOTER) output = '\n'.join(map(lambda i: (i if isinstance(i, str) else i.decode('utf8', 'ignore')), line_buf)) return output def process_subsection(self, section, key, header, format_, footer): if key in section: self.line_buf.append(header) for item in sorted( section.get(key), key=lambda x: x["name"] if isinstance(x, dict) else '' ): self.line_buf.append(format_ % item) if len(footer) > 0: self.line_buf.append(footer) class HTMLReport(PlainTextReport): """Will generate a HTML report from a top_level Report object""" HEADER = """ Sos System Report \n""" FOOTER = "" LEAF = '
  • %(name)s
  • ' ALERT = "
  • %s
  • " NOTE = "
  • %s
  • " PLUGLISTHEADER = "

    Loaded Plugins:

    " PLUGLISTITEM = '\n' PLUGLISTSEP = "\n" PLUGLISTMAXITEMS = 5 PLUGLISTFOOTER = "
    {name}
    " PLUGINFORMAT = '

    Plugin {name}

    ' PLUGDIVIDER = "
    \n" subsections = ( (Command, LEAF, "

    Commands executed:

      ", "
    "), (CopiedFile, LEAF, "

    Files copied:

      ", "
    "), (CreatedFile, LEAF, "

    Files created:

      ", "
    "), (Alert, ALERT, "

    Alerts:

      ", "
    "), (Note, NOTE, "

    Notes:

      ", "
    "), ) class JSONReport(PlainTextReport): """Will generate a JSON report from a top_level Report object""" def unicode(self): output = json.dumps(self.report_data, indent=4, ensure_ascii=False) return output # vim: set et ts=4 sw=4 : sos-4.8.0/sos/presets/0000775000175000017500000000000014660147624013260 5ustar arifarifsos-4.8.0/sos/presets/redhat/0000775000175000017500000000000014660147624014527 5ustar arifarifsos-4.8.0/sos/presets/redhat/__init__.py0000664000175000017500000000773214660147624016651 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.options import SoSOptions from sos.presets import PresetDefaults RHEL_RELEASE_STR = "Red Hat Enterprise Linux" _opts_verify = SoSOptions(verify=True) _cb_profiles = ['boot', 'storage', 'system'] _cb_plugopts = ['boot.all-images=on', 'rpm.rpmva=on', 'rpm.rpmdb=on'] RHV = "rhv" RHV_DESC = "Red Hat Virtualization" RHEL = "rhel" RHEL_DESC = RHEL_RELEASE_STR RHOSP = "rhosp" RHOSP_DESC = "Red Hat OpenStack Platform" RHOSP_OPTS = SoSOptions(plugopts=[ 'process.lsof=off', 'networking.ethtool-namespaces=False', 'networking.namespaces=200']) RHOCP = "ocp" RHOCP_DESC = "OpenShift Container Platform by Red Hat" RHOCP_OPTS = SoSOptions( skip_plugins=['cgroups'], container_runtime='crio', no_report=True, log_size=100, plugopts=[ 'crio.timeout=600', 'networking.timeout=600', 'networking.ethtool-namespaces=False', 'networking.namespaces=200' ]) RH_CFME = "cfme" RH_CFME_DESC = "Red Hat CloudForms" RH_SATELLITE = "satellite" RH_SATELLITE_DESC = "Red Hat Satellite" SAT_OPTS = SoSOptions(log_size=100, plugopts=['apache.log=on']) AAPEDA = 'aap_eda' AAPEDA_DESC = 'Ansible Automation Platform Event Driven Controller' AAPEDA_OPTS = SoSOptions( enable_plugins=['containers_common'], plugopts=[ 'containers_common.rootlessusers=eda' ]) AAPEDA_NOTE = ('Collects \'eda\' user output for the containers_common plugin.' ' If you need more users, do not forget to add \'eda\' ' 'to your own list for the \'rootlessusers\' option.') AAPCONTROLLER = 'aap_controller' AAPCONTROLLER_DESC = 'Ansible Automation Platform Controller' AAPCONTROLLER_OPTS = SoSOptions( enable_plugins=['containers_common'], plugopts=[ 'containers_common.rootlessusers=awx' ]) AAPCONTROLLER_NOTE = ('Collects \'awx\' user output for the containers_common' 'pluging. If you need more users, do not forget to add' '\'awx\' to your own list for the \'rootlessusers\' ' 'option.') CB = "cantboot" CB_DESC = "For use when normal system startup fails" CB_OPTS = SoSOptions( verify=True, all_logs=True, profiles=_cb_profiles, plugopts=_cb_plugopts ) CB_NOTE = "Data collection will be limited to a boot-affecting scope" NOTE_SIZE = "This preset may increase report size" NOTE_TIME = "This preset may increase report run time" NOTE_SIZE_TIME = "This preset may increase report size and run time" RHEL_PRESETS = { AAPEDA: PresetDefaults(name=AAPEDA, desc=AAPEDA_DESC, opts=AAPEDA_OPTS, note=AAPEDA_NOTE), AAPCONTROLLER: PresetDefaults(name=AAPCONTROLLER, desc=AAPCONTROLLER_DESC, opts=AAPCONTROLLER_OPTS, note=AAPCONTROLLER_NOTE), RHV: PresetDefaults(name=RHV, desc=RHV_DESC, note=NOTE_TIME, opts=_opts_verify), RHEL: PresetDefaults(name=RHEL, desc=RHEL_DESC), RHOSP: PresetDefaults(name=RHOSP, desc=RHOSP_DESC, opts=RHOSP_OPTS), RHOCP: PresetDefaults(name=RHOCP, desc=RHOCP_DESC, note=NOTE_SIZE_TIME, opts=RHOCP_OPTS), RH_CFME: PresetDefaults(name=RH_CFME, desc=RH_CFME_DESC, note=NOTE_TIME, opts=_opts_verify), RH_SATELLITE: PresetDefaults(name=RH_SATELLITE, desc=RH_SATELLITE_DESC, note=NOTE_TIME, opts=SAT_OPTS), CB: PresetDefaults(name=CB, desc=CB_DESC, note=CB_NOTE, opts=CB_OPTS) } # vim: set et ts=4 sw=4 : sos-4.8.0/sos/presets/__init__.py0000664000175000017500000000771414660147624015402 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import os from sos.options import SoSOptions PRESETS_PATH = "/etc/sos/presets.d" #: Constants for on-disk preset fields DESC = "desc" NOTE = "note" OPTS = "args" class PresetDefaults(): """Preset command line defaults to allow for quick reference to sets of commonly used options :param name: The name of the new preset :type name: ``str`` :param desc: A description for the new preset :type desc: ``str`` :param note: Note for the new preset :type note: ``str`` :param opts: Options set for the new preset :type opts: ``SoSOptions`` """ #: Preset name, used for selection name = None #: Human readable preset description desc = None #: Notes on preset behaviour note = None #: Options set for this preset opts = SoSOptions() #: ``True`` if this preset if built-in or ``False`` otherwise. builtin = True def __str__(self): """Return a human readable string representation of this ``PresetDefaults`` object. """ return (f"name={self.name} desc={self.desc} note={self.note} " f"opts=({str(self.opts)})") def __repr__(self): """Return a machine readable string representation of this ``PresetDefaults`` object. """ return (f"PresetDefaults(name='{self.name}' desc='{self.desc}' " f"note='{self.note}' opts=({repr(self.opts)})") def __init__(self, name="", desc="", note=None, opts=SoSOptions()): """Initialise a new ``PresetDefaults`` object with the specified arguments. :returns: The newly initialised ``PresetDefaults`` """ self.name = name self.desc = desc self.note = note self.opts = opts def write(self, presets_path): """Write this preset to disk in JSON notation. :param presets_path: the directory where the preset will be written :type presets_path: ``str`` """ if self.builtin: raise TypeError("Cannot write built-in preset") # Make dictionaries of PresetDefaults values odict = self.opts.dict() pdict = {self.name: {DESC: self.desc, NOTE: self.note, OPTS: odict}} if not os.path.exists(presets_path): os.makedirs(presets_path, mode=0o755) with open(os.path.join(presets_path, self.name), "w", encoding='utf-8') as pfile: json.dump(pdict, pfile) def delete(self, presets_path): """Delete a preset from disk :param presets_path: the directory where the preset is saved :type presets_path: ``str`` """ os.unlink(os.path.join(presets_path, self.name)) NO_PRESET = 'none' NO_PRESET_DESC = 'Do not load a preset' NO_PRESET_NOTE = 'Use to disable automatically loaded presets' SMALL_PRESET = 'minimal' SMALL_PRESET_DESC = ('Small and quick report that reduces sos report resource ' 'consumption') SMALL_PRESET_NOTE = ('May be useful for low-resource systems, but may not ' 'provide sufficient data for analysis') SMALL_PRESET_OPTS = SoSOptions(log_size=10, journal_size=10, plugin_timeout=30, command_timeout=30, low_priority=True) GENERIC_PRESETS = { NO_PRESET: PresetDefaults( name=NO_PRESET, desc=NO_PRESET_DESC, note=NO_PRESET_NOTE, opts=SoSOptions() ), SMALL_PRESET: PresetDefaults( name=SMALL_PRESET, desc=SMALL_PRESET_DESC, note=SMALL_PRESET_NOTE, opts=SMALL_PRESET_OPTS ) } # vim: set et ts=4 sw=4 : sos-4.8.0/sos/options.py0000664000175000017500000003234214660147624013644 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from argparse import Action from configparser import (ConfigParser, ParsingError, Error, DuplicateOptionError) def _is_seq(val): """Return true if val is an instance of a known sequence type. """ val_type = type(val) return val_type is list or val_type is tuple def str_to_bool(val): _val = val.lower() if _val in ['true', 'on', 'yes']: return True if _val in ['false', 'off', 'no']: return False return None class SoSOptions(): def _merge_opt(self, opt, src, is_default): def _unset(val): return (val == "" or val is None) if hasattr(src, opt): newvalue = getattr(src, opt) oldvalue = getattr(self, opt) # overwrite value iff: # - we replace unset option by a real value # - new default is set, or # - non-sequential variable keeps its default value if (_unset(oldvalue) and not _unset(newvalue)) or \ is_default or \ ((opt not in self._nondefault) and (not _is_seq(newvalue))): # Overwrite atomic values setattr(self, opt, newvalue) if is_default: self._nondefault.discard(opt) else: self._nondefault.add(opt) elif _is_seq(newvalue): # Concatenate sequence types setattr(self, opt, newvalue + oldvalue) def _merge_opts(self, src, is_default): if not isinstance(src, dict): src = vars(src) for arg in self.arg_names: self._merge_opt(arg, src, is_default) def __str(self, quote=False, sep=" ", prefix="", suffix=""): """Format a SoSOptions object as a human or machine readable string. :param quote: quote option values :param sep: list separator string :param prefix: arbitrary prefix string :param suffix: arbitrary suffix string :param literal: print values as Python literals """ args = prefix arg_fmt = "=%s" for arg in self.arg_names: args += arg + arg_fmt + sep args.strip(sep) vals = [getattr(self, arg) for arg in self.arg_names] if not quote: # Convert Python source notation for sequences into plain strings vals = [",".join(v) if _is_seq(v) else v for v in vals] else: # Only quote strings if quote=False vals = [f"'{v}'" if isinstance(v, str) else v for v in vals] return (args % tuple(vals)).strip(sep) + suffix def __str__(self): return self.__str() def __repr__(self): return self.__str(quote=True, sep=", ", prefix="SoSOptions(", suffix=")") def __init__(self, arg_defaults={}, **kwargs): """Initialise a new ``SoSOptions`` object from keyword arguments. Initialises the new object with values taken from keyword arguments matching the names of ``SoSOptions`` attributes. A ``ValueError`` is raised is any of the supplied keyword arguments does not correspond to a known ``SoSOptions` attribute name. :param *kwargs: a list of ``SoSOptions`` keyword args. :returns: the new ``SoSOptions`` object. """ self.arg_defaults = arg_defaults self.arg_names = list(arg_defaults.keys()) self._nondefault = set() # first load the defaults, if supplied for arg in self.arg_defaults: setattr(self, arg, self.arg_defaults[arg]) # next, load any kwargs for arg, kwarg in kwargs.items(): self.arg_names.append(arg) setattr(self, arg, kwarg) @classmethod def from_args(cls, args, arg_defaults={}): """Initialise a new SoSOptions object from a ``Namespace`` obtained by parsing command line arguments. :param args: parsed command line arguments :returns: an initialised SoSOptions object :returntype: SoSOptions """ opts = SoSOptions(**vars(args), arg_defaults=arg_defaults) opts._merge_opts(args, True) return opts @classmethod def _opt_to_args(cls, opt, val): """Convert a named option and optional value to command line argument notation, correctly handling options that take no value or that have special representations (e.g. verify and verbose). """ no_value = ( "alloptions", "allow-system-changes", "all-logs", "batch", "build", "debug", "experimental", "list-plugins", "list-presets", "list-profiles", "no-report", "no-env-vars", "quiet", "verify" ) count = ("verbose",) if opt in no_value: return [f"--{opt}"] if opt in count: return [f"--{opt}" for d in range(0, int(val))] return [f"--{opt}={val}"] def _convert_to_type(self, key, val, conf): """Ensure that the value read from a config file is the proper type for consumption by the component, as defined by arg_defaults. Params: :param key: The key in arg_defaults we need to match the type of :param val: The value to be converted to a particular type :param conf: File values are being loaded from """ if isinstance(self.arg_defaults[key], type(val)): return val if isinstance(self.arg_defaults[key], list): return list(val.split(',')) if isinstance(self.arg_defaults[key], bool): val = str_to_bool(val) if val is None: raise Exception( f"Value of '{key}' in {conf} must be True or False or " "analagous") return val if isinstance(self.arg_defaults[key], int): try: return int(val) except ValueError: raise Exception(f"Value of '{key}' in {conf} must be integer") return val def update_from_conf(self, config_file, component): """Read the provided config_file and update options from that. Positional arguments: :param config_file: Filepath to the config file :param component: Which component (section) to load """ def _update_from_section(section, config): if config.has_section(section): odict = dict(config.items(section)) # handle verbose explicitly if 'verbose' in odict.keys(): odict['verbosity'] = int(odict.pop('verbose')) # convert options names # unify some of them if multiple variants of the # cmdoption exist rename_opts = { 'name': 'label', 'plugin_option': 'plugopts', 'profile': 'profiles' } for key in list(odict): if '-' in key: odict[key.replace('-', '_')] = odict.pop(key) if key in rename_opts: odict[rename_opts[key]] = odict.pop(key) # set the values according to the config file for key, val in odict.items(): # most option values do not tolerate spaces, special # exception however for --keywords which we do want to # support phrases, and thus spaces, for if isinstance(val, str) and key != 'keywords': val = val.replace(' ', '') if key not in self.arg_defaults: # read an option that is not loaded by the current # SoSComponent print(f"Unknown option '{key}' in section '{section}'") continue val = self._convert_to_type(key, val, config_file) setattr(self, key, val) config = ConfigParser() try: try: with open(config_file, encoding='utf-8') as f: config.read_file(f, config_file) except DuplicateOptionError as err: raise Exception(f"Duplicate option '{err.option}' in section " f"'{err.section}' in file {config_file}") except (ParsingError, Error): raise Exception(f'Failed to parse configuration file ' f'{config_file}') except (OSError, IOError) as e: print( f'WARNING: Unable to read configuration file {config_file} : ' f'{e.args[1]}' ) _update_from_section("global", config) _update_from_section(component, config) if config.has_section("plugin_options") and hasattr(self, 'plugopts'): # pylint: disable=no-member for key, val in config.items("plugin_options"): if not key.split('.')[0] in self.skip_plugins: self.plugopts.append(key + '=' + val) def merge(self, src, skip_default=True): """Merge another set of ``SoSOptions`` into this object. Merge two ``SoSOptions`` objects by setting unset or default values to their value in the ``src`` object. :param src: the ``SoSOptions`` object to copy from :param is_default: ``True`` if new default values are to be set. """ for arg in self.arg_names: if not hasattr(src, arg): continue if getattr(src, arg) is not None or not skip_default: self._merge_opt(arg, src, False) def dict(self, preset_filter=True): """Return this ``SoSOptions`` option values as a dictionary of argument name to value mappings. :returns: a name:value dictionary of option values. """ odict = {} for arg in self.arg_names: value = getattr(self, arg) # Do not attempt to store preset option values in presets if preset_filter: if arg in ('add_preset', 'del_preset', 'desc', 'note'): value = None odict[arg] = value return odict def to_args(self): """Return command arguments for this object. Return a list of the non-default options of this ``SoSOptions`` object in ``sos report`` command line argument notation: ``["--all-logs", "-vvv"]`` """ def has_value(name, value): """ Test for non-null option values. """ null_values = ("False", "None", "[]", '""', "''", "0") if not value or value in null_values: return False if name == 'plugopts' and value: return True if name in self.arg_defaults: if str(value) == str(self.arg_defaults[name]): return False return True def filter_opt(name, value): """ Filter out preset and null-valued options. """ if name in ("add_preset", "del_preset", "desc", "note"): return False # Exception list for options that still need to be reported when 0 if name in ['log_size', 'plugin_timeout', 'cmd_timeout'] \ and value == 0: return True return has_value(name, value) def argify(name, value): """ Convert sos option notation to command line arguments. """ # Handle --verbosity specially if name.startswith("verbosity"): arg = "-" + int(value) * "v" return arg name = name.replace("_", "-") value = ",".join(value) if _is_seq(value) else value if value is not True: opt = f"{name} {value}" else: opt = name arg = "--" + opt if len(opt) > 1 else "-" + opt return arg opt_items = sorted(self.dict().items(), key=lambda x: x[0]) return [argify(n, v) for (n, v) in opt_items if filter_opt(n, v)] class SosListOption(Action): """Allow to specify comma delimited list of plugins""" def __call__(self, parser, namespace, values, option_string=None): items = list(values.split(',')) if getattr(namespace, self.dest): items += getattr(namespace, self.dest) setattr(namespace, self.dest, items) class ClusterOption(): """Used to store/manipulate options for cluster profiles.""" def __init__(self, name, value, opt_type, cluster, description=None): self.name = name self.value = value self.opt_type = opt_type self.cluster = cluster self.description = description sos-4.8.0/sos/help/0000775000175000017500000000000014660147624012523 5ustar arifarifsos-4.8.0/sos/help/__init__.py0000664000175000017500000002541214660147624014640 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import inspect import importlib import sys import os from collections import OrderedDict from textwrap import fill from sos.component import SoSComponent from sos.policies import import_policy from sos.report.plugins import Plugin from sos.utilities import bold, ImporterHelper try: TERMSIZE = min(os.get_terminal_size().columns, 120) except Exception: TERMSIZE = 120 class SoSHelper(SoSComponent): """Provide better, more in-depth help for specific parts of sos than is provided in either standard --help output or in manpages. """ desc = 'Detailed help infomation' configure_logging = False load_policy = False load_probe = False arg_defaults = { 'topic': '' } def __init__(self, parser, args, cmdline): super().__init__(parser, args, cmdline) self.topic = self.opts.topic @classmethod def add_parser_options(cls, parser): parser.usage = 'sos help TOPIC [options]' help_grp = parser.add_argument_group( 'Help Information Options', 'These options control what detailed information is displayed' ) help_grp.add_argument('topic', metavar='TOPIC', default='', nargs='?', help=('name of the topic or component to show ' 'help for')) def sanitize_topic_component(self): _com = self.opts.topic.split('.')[0] _replace = { 'clean': 'cleaner', 'mask': 'cleaner', 'collect': 'collector' } if _com in _replace: self.opts.topic = self.opts.topic.replace(_com, _replace[_com]) def execute(self): if not self.opts.topic: self.display_self_help() sys.exit(0) # standardize the command to the module naming pattern self.sanitize_topic_component() try: klass = self.get_obj_for_topic() except Exception as err: print(f"Could not load help for '{self.opts.topic}': {err}") sys.exit(1) if klass: try: ht = HelpSection() klass.display_help(ht) ht.display() except Exception as err: print(f"Error loading help: {err}") else: print(f"No help section found for '{self.opts.topic}'") def get_obj_for_topic(self): """Based on the help topic we're after, try to smartly decide which object we need to manipulate in order to get help information. """ static_map = { 'report': 'SoSReport', 'report.plugins': 'Plugin', 'cleaner': 'SoSCleaner', 'collector': 'SoSCollector', 'collector.transports': 'RemoteTransport', 'collector.clusters': 'Cluster', 'policies': 'Policy' } cls = None if self.opts.topic in static_map: mod = importlib.import_module('sos.' + self.opts.topic) cls = getattr(mod, static_map[self.opts.topic]) else: _help = { 'report.plugins.': self._get_plugin_variant, 'policies.': self._get_policy_by_name, 'collector.transports.': self._get_collect_transport, 'collector.clusters.': self._get_collect_cluster, } for _sec, value in _help.items(): if self.opts.topic.startswith(_sec): cls = value() break return cls def _get_collect_transport(self): from sos.collector.sosnode import TRANSPORTS _transport = self.opts.topic.split('.')[-1] if _transport in TRANSPORTS: return TRANSPORTS[_transport] return None def _get_collect_cluster(self): from sos.collector import SoSCollector import sos.collector.clusters clusters = SoSCollector._load_modules(sos.collector.clusters, 'clusters') for cluster in clusters: if cluster[0] == self.opts.topic.split('.')[-1]: return cluster[1] return None def _get_plugin_variant(self): mod = importlib.import_module('sos.' + self.opts.topic) self.load_local_policy() mems = inspect.getmembers(mod, inspect.isclass) plugins = [m[1] for m in mems if issubclass(m[1], Plugin)] for plugin in plugins: if plugin.__subclasses__(): cls = self.policy.match_plugin(plugin.__subclasses__()) return cls return None def _get_policy_by_name(self): _topic = self.opts.topic.split('.')[-1] # mimic policy loading to discover all policiy classes without # needing to manually define each here import sos.policies.distros _helper = ImporterHelper(sos.policies.distros) for mod in _helper.get_modules(): for policy in import_policy(mod): _p = policy.__name__.lower().replace('policy', '') if _p == _topic: return policy return None def display_self_help(self): """Displays the help information for this component directly, that is help for `sos help`. """ self_help = HelpSection( 'Detailed help for sos help', ('The \'help\' sub-command is used to provide more detailed ' 'information on different sub-commands available to sos as well ' 'as different components at play within those sub-commands.') ) self_help.add_text( 'SoS - officially pronounced "ess-oh-ess" - is a diagnostic and ' 'supportability utility used by several Linux distributions as an ' 'easy-to-use tool for standardized data collection. The most known' f' component of which is {bold("sos report")} (formerly sosreport)' ' which is used to collect troubleshooting information into an ' 'archive for review by sysadmins or technical support teams.' ) subsect = self_help.add_section('How to search using sos help') usage = bold('$component.$topic.$subtopic') subsect.add_text( 'To get more information on a given topic, use the form ' f'\'{usage}\'.' ) rep_ex = bold('sos help report.plugins.kernel') subsect.add_text(f"For example '{rep_ex}' will provide more " "information on the kernel plugin for the report " "function.") avail_help = self_help.add_section('Available Help Sections') avail_help.add_text( 'The following help sections are available. Additional help' ' topics and subtopics may be displayed within their respective ' 'help section.\n' ) sections = { 'report': 'Detailed help on the report command', 'report.plugins': 'Information on the plugin design of sos', 'report.plugins.$plugin': 'Information on a specific $plugin', 'clean': 'Detailed help on the clean command', 'collect': 'Detailed help on the collect command', 'policies': 'How sos operates on different distributions' } for sect, value in sections.items(): avail_help.add_text(f"\t{bold(sect):<36}{value}", newline=False) self_help.display() class HelpSection(): """This class is used to build the output displayed by `sos help` in a standard fashion that provides easy formatting controls. """ def __init__(self, title='', content='', indent=''): """ :param title: The title of the output section, will be prominently displayed :type title: ``str`` :param content: The text content to be displayed with this section :type content: ``str`` :param indent: If the section should be nested, set this to a multiple of 4. :type indent: ``int`` """ self.title = title self.content = content self.indent = indent self.sections = OrderedDict() def set_title(self, title): """Set or override the title for this help section :param title: The name to set for this help section :type title: ``str`` """ self.title = title def add_text(self, content, newline=True): """Add body text to this section. If content for this section already exists, append the new ``content`` after a newline. :param content: The text to add to the section :type content: ``str`` """ if self.content: ln = '\n\n' if newline else '\n' content = ln + content self.content += content def add_section(self, title, content='', indent=''): """Add a section of text to the help section that will be displayed when the HelpSection object is printed. Sections will be printed *in the order added*. This will return a subsection object with which block(s) of text may be added to the subsection associated with ``title``. :param title: The title of the subsection being added :type title: ``str`` :param content: The text the new section should contain :type content: ``str`` :returns: The newly created subsection for ``title`` :rtype: ``HelpSection`` """ self._add_section(title, content, indent) return self.sections[title] def _add_section(self, title, content='', indent=''): """Internal method used to add a new subsection to this help output :param title: The title of the subsection being added :type title: ``str` """ if title in self.sections: raise Exception('A section with that title already exists') self.sections[title] = HelpSection(title, content, indent) def display(self): """Print the HelpSection contents, including any subsections, to console. """ print(fill( bold(self.title), width=TERMSIZE, initial_indent=self.indent )) for ln in self.content.splitlines(): print(fill(ln, width=TERMSIZE, initial_indent=self.indent)) for section in self.sections: print('') self.sections[section].display() sos-4.8.0/sos/policies/0000775000175000017500000000000014660147624013402 5ustar arifarifsos-4.8.0/sos/policies/__init__.py0000664000175000017500000005230014660147624015513 0ustar arifarif import logging import os import platform import time import json import tempfile import random import string import sys from pwd import getpwuid from textwrap import fill from sos.presets import (NO_PRESET, GENERIC_PRESETS, PRESETS_PATH, PresetDefaults, DESC, NOTE, OPTS) from sos.policies.package_managers import PackageManager from sos.utilities import (ImporterHelper, import_module, get_human_readable, bold) from sos.report.plugins import IndependentPlugin, ExperimentalPlugin from sos.options import SoSOptions from sos import _sos as _ def import_policy(name): policy_fqname = f"sos.policies.distros.{name}" try: return import_module(policy_fqname, Policy) except ImportError: return None def load(cache={}, sysroot=None, init=None, probe_runtime=True, remote_exec=None, remote_check=''): if 'policy' in cache: return cache.get('policy') import sos.policies.distros helper = ImporterHelper(sos.policies.distros) for module in helper.get_modules(): for policy in import_policy(module): if policy.check(remote=remote_check): cache['policy'] = policy(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) if sys.platform != 'linux': raise Exception("SoS is not supported on this platform") if 'policy' not in cache: cache['policy'] = sos.policies.distros.GenericLinuxPolicy() return cache['policy'] class Policy(): """Policies represent distributions that sos supports, and define the way in which sos behaves on those distributions. A policy should define at minimum a way to identify the distribution, and a package manager to allow for package based plugin enablement. Policies also control preferred ContainerRuntime()'s, upload support to default locations for distribution vendors, disclaimer text, and default presets supported by that distribution or vendor's products. Every Policy will also need at least one "tagging class" for plugins. :param sysroot: Set the sysroot for the system, if not / :type sysroot: ``str`` or ``None`` :param probe_runtime: Should the Policy try to load a ContainerRuntime :type probe_runtime: ``bool`` :param remote_exec: If this policy is loaded for a remote node, use this to facilitate executing commands via the SoSTransport in use :type remote_exec: ``SoSTranport.run_command()`` :cvar distro: The name of the distribution the Policy represents :vartype distro: ``str`` :cvar vendor: The name of the vendor producing the distribution :vartype vendor: ``str`` :cvar vendor_urls: List of URLs for the vendor's website, or support portal :vartype vendor_urls: ``list`` of ``tuples`` formatted ``(``description``, ``url``)`` :cvar vendor_text: Additional text to add to the banner message :vartype vendor_text: ``str`` :cvar name_pattern: The naming pattern to be used for naming archives generated by sos. Values of `legacy`, and `friendly` are preset patterns. May also be set to an explicit custom pattern, see `get_archive_name()` :vartype name_pattern: ``str`` """ msg = _("""\ This command will collect system configuration and diagnostic information \ from this %(distro)s system. For more information on %(vendor)s visit: %(vendor_urls)s The generated archive may contain data considered sensitive and its content \ should be reviewed by the originating organization before being passed to \ any third party. %(changes_text)s %(vendor_text)s """) distro = "Unknown" vendor = "Unknown" vendor_urls = [('Example URL', "http://www.example.com/")] vendor_text = "" PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" name_pattern = 'legacy' presets = {"": PresetDefaults()} presets_path = PRESETS_PATH _in_container = False def __init__(self, sysroot=None, probe_runtime=True, remote_exec=None): """Subclasses that choose to override this initializer should call super() to ensure that they get the required platform bits attached. super(SubClass, self).__init__(). Policies that require runtime tests to construct PATH must call self.set_exec_path() after modifying PATH in their own initializer.""" self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') self._parse_uname() self.case_id = None self.probe_runtime = probe_runtime self.package_manager = PackageManager() self.valid_subclasses = [IndependentPlugin] self.remote_exec = remote_exec if not self.remote_exec: self.set_exec_path() self.sysroot = sysroot self.register_presets(GENERIC_PRESETS) @classmethod def check(cls, remote=''): """ This function is responsible for determining if the underlying system is supported by this policy. If `remote` is provided, it should be the contents of os-release from a remote host, or a similar vendor-specific file that can be used in place of a locally available file. :returns: ``True`` if the Policy should be loaded, else ``False`` :rtype: ``bool`` """ raise NotImplementedError @property def forbidden_paths(self): """This property is used to determine the list of forbidden paths set by the policy. Note that this property will construct a *cumulative* list based on all subclasses of a given policy. :returns: All patterns of policy forbidden paths :rtype: ``list`` """ if not hasattr(self, '_forbidden_paths'): self._forbidden_paths = [] for cls in self.__class__.__mro__: if hasattr(cls, 'set_forbidden_paths'): self._forbidden_paths.extend(cls.set_forbidden_paths()) return list(set(self._forbidden_paths)) @classmethod def set_forbidden_paths(cls): """Use this to *append* policy-specifc forbidden paths that apply to all plugins. Setting this classmethod on an invidual policy will *not* override subclass-specific paths """ return [ '*.egg', '*.pyc', '*.pyo', '*.swp' ] def in_container(self): """Are we running inside a container? :returns: ``True`` if in a container, else ``False`` :rtype: ``bool`` """ return self._in_container def dist_version(self): """ Return the OS version """ pass def get_preferred_archive(self): """ Return the class object of the prefered archive format for this platform """ from sos.archive import TarFileArchive return TarFileArchive def get_archive_name(self): """ This function should return the filename of the archive without the extension. This uses the policy's `name_pattern` attribute to determine the name. There are two pre-defined naming patterns - `legacy` and `friendly` that give names like the following: * legacy - `sosreport-tux.123456-20171224185433` * friendly - `sosreport-tux-mylabel-123456-2017-12-24-ezcfcop.tar.xz` A custom name_pattern can be used by a policy provided that it defines name_pattern using a format() style string substitution. Usable substitutions are: * name - the short hostname of the system * label - the label given by --label * case - the case id given by --case-id * rand - a random string of 7 alpha characters Note that if a datestamp is needed, the substring should be set in `name_pattern` in the format accepted by ``strftime()``. :returns: A name to be used for the archive, as expanded from the Policy `name_pattern` :rtype: ``str`` """ name = self.get_local_name().split('.')[0] # pylint: disable=no-member case = self.case_id label = self.commons['cmdlineopts'].label date = '' rand = ''.join(random.choice(string.ascii_lowercase) for x in range(7)) if self.name_pattern == 'legacy': case = '.' + case if case else '' date = '-%Y%m%d%H%M%S' nstr = f"sosreport-{name}{case}{date}" elif self.name_pattern == 'friendly': case = '-' + case if case else '' label = '-' + label if label else '' date = '-%Y-%m-%d' nstr = f"sosreport-{name}{label}{case}{date}-{rand}" else: nstr = self.name_pattern # pylint: disable-next=no-member return self.sanitize_filename(time.strftime(nstr)) # for some specific binaries like "xz", we need to determine package # providing it; that is policy specific. By default return the binary # name itself until particular policy overwrites it def _get_pkg_name_for_binary(self, binary): return binary def get_tmp_dir(self, opt_tmp_dir): if not opt_tmp_dir: return tempfile.gettempdir() return opt_tmp_dir def match_plugin(self, plugin_classes): """Determine what subclass of a Plugin should be used based on the tagging classes assigned to the Plugin :param plugin_classes: The classes that the Plugin subclasses :type plugin_classes: ``list`` :returns: The first tagging class that matches one of the Policy's `valid_subclasses` :rtype: ``PluginDistroTag`` """ if len(plugin_classes) > 1: for p in plugin_classes: # Give preference to the first listed tagging class # so that e.g. UbuntuPlugin is chosen over DebianPlugin # on an Ubuntu installation. if issubclass(p, self.valid_subclasses[0]): return p return plugin_classes[0] def validate_plugin(self, plugin_class, experimental=False): """ Verifies that the plugin_class should execute under this policy :param plugin_class: The tagging class being checked :type plugin_class: ``PluginDistroTag`` :returns: ``True`` if the `plugin_class` is allowed by the policy :rtype: ``bool`` """ valid_subclasses = [IndependentPlugin] + self.valid_subclasses if experimental: valid_subclasses += [ExperimentalPlugin] return any(issubclass(plugin_class, class_) for class_ in valid_subclasses) def pre_work(self): """ This function is called prior to collection. """ pass def post_work(self): """ This function is called after the sos report has been generated. """ pass def pkg_by_name(self, pkg): """Wrapper to retrieve a package from the Policy's package manager :param pkg: The name of the package :type pkg: ``str`` :returns: The first package that matches `pkg` :rtype: ``str`` """ return self.package_manager.pkg_by_name(pkg) def _parse_uname(self): (system, node, release, version, machine, _) = platform.uname() self.system = system self.hostname = node self.release = release self.smp = version.split()[1] == "SMP" self.machine = machine def set_commons(self, commons): """Set common host data for the Policy to reference """ self.commons = commons def _set_PATH(self, path): os.environ['PATH'] = path def set_exec_path(self): self._set_PATH(self.PATH) def is_root(self): """This method should return true if the user calling the script is considered to be a superuser :returns: ``True`` if user is superuser, else ``False`` :rtype: ``bool`` """ return os.getuid() == 0 def get_preferred_hash_name(self): """Returns the string name of the hashlib-supported checksum algorithm to use""" return "sha256" @classmethod def display_help(cls, section): section.set_title('SoS Policies') section.add_text( 'Policies help govern how SoS operates on across different distri' 'butions of Linux. They control aspects such as plugin enablement,' ' $PATH determination, how/which package managers are queried, ' 'default upload specifications, and more.' ) section.add_text( "When SoS intializes most functions, for example " f"{bold('sos report')} and {bold('sos collect')}, one " "of the first operations is to determine the correct policy to " "load for the local system. Policies will determine the proper " "package manager to use, any applicable container runtime(s), and " "init systems so that SoS and report plugins can properly function" " for collections. Generally speaking a single policy will map to" " a single distribution; for example there are separate policies " "for Debian, Ubuntu, RHEL, and Fedora." ) section.add_text( "It is currently not possible for users to directly control which " "policy is loaded." ) pols = { 'policies.cos': 'The Google Cloud-Optimized OS distribution', 'policies.debian': 'The Debian distribution', 'policies.redhat': ('Red Hat family distributions, not necessarily' ' including forks'), 'policies.ubuntu': 'Ubuntu/Canonical distributions' } seealso = section.add_section('See Also') seealso.add_text( "For more information on distribution policies, see below\n" ) for pol, value in pols.items(): seealso.add_text(f"{' ':>8}{pol:<20}{value:<30}", newline=False) def display_results(self, archive, directory, checksum, archivestat=None, map_file=None): """Display final information about a generated archive :param archive: The name of the archive that was generated :type archive: ``str`` :param directory: The build directory for sos if --build was used :type directory: ``str`` :param checksum: The checksum of the archive :type checksum: ``str`` :param archivestat: stat() information for the archive :type archivestat: `os.stat_result` :param map_file: If sos clean was invoked, the location of the mapping file for this run :type map_file: ``str`` """ # Logging is shut down, but there are some edge cases where automation # does not capture printed output (e.g. avocado CI). Use the ui_log to # still print to console in this case. # make sure a report exists if not archive and not directory: return False if map_file: self.ui_log.info( _(f"\nA mapping of obfuscated elements is available at" f"\n\t{map_file}") ) if archive: self.ui_log.info( _(f"\nYour sos report has been generated and saved in:" f"\n\t{archive}\n") ) self.ui_log.info( _(f" Size\t{get_human_readable(archivestat.st_size)}") ) self.ui_log.info( _(f" Owner\t{getpwuid(archivestat.st_uid).pw_name}") ) else: self.ui_log.info( _(f"Your sos report build tree has been generated in:" f"\n\t{directory}\n") ) if checksum: self.ui_log.info(f" {self.get_preferred_hash_name()}\t{checksum}") self.ui_log.info( _("\nPlease send this file to your support representative.\n") ) return None def get_msg(self): """This method is used to prepare the preamble text to display to the user in non-batch mode. If your policy sets self.distro that text will be substituted accordingly. You can also override this method to do something more complicated. :returns: Formatted banner message string :rtype: ``str`` """ if self.commons['cmdlineopts'].allow_system_changes: changes_text = "Changes CAN be made to system configuration." else: changes_text = "No changes will be made to system configuration." width = 72 _msg = self.msg % {'distro': self.distro, 'vendor': self.vendor, 'vendor_urls': self._fmt_vendor_urls(), 'vendor_text': self.vendor_text, 'tmpdir': self.commons['tmpdir'], 'changes_text': changes_text} _fmt = "" for line in _msg.splitlines(): _fmt = _fmt + fill(line, width, replace_whitespace=False) + '\n' return _fmt def _fmt_vendor_urls(self): """Formats all items in the ``vendor_urls`` class attr into a usable string for the banner message. :returns: Formatted string of URLS :rtype: ``str`` """ width = max(len(v[0]) for v in self.vendor_urls) return "\n".join( f"\t{url[0]:<{width}} : {url[1]}" for url in self.vendor_urls ) def register_presets(self, presets, replace=False): """Add new presets to this policy object. Merges the presets dictionary ``presets`` into this ``Policy`` object, or replaces the current presets if ``replace`` is ``True``. ``presets`` should be a dictionary mapping ``str`` preset names to ```` objects specifying the command line defaults. :param presets: dictionary of presets to add or replace :param replace: replace presets rather than merge new presets. """ if replace: self.presets = {} self.presets.update(presets) def find_preset(self, preset): """Find a preset profile matching the specified preset string. :param preset: a string containing a preset profile name. :returns: a matching PresetProfile. """ # FIXME: allow fuzzy matching? for match, value in self.presets.items(): if match == preset: return value return None def probe_preset(self): """Return a ``PresetDefaults`` object matching the runing host. Stub method to be implemented by derived policy classes. :returns: a ``PresetDefaults`` object. """ return self.presets[NO_PRESET] def load_presets(self, presets_path=None): """Load presets from disk. Read JSON formatted preset data from the specified path, or the default location at ``/var/lib/sos/presets``. :param presets_path: a directory containing JSON presets. """ presets_path = presets_path or self.presets_path if not os.path.exists(presets_path): return for preset_path in os.listdir(presets_path): preset_path = os.path.join(presets_path, preset_path) with open(preset_path, encoding='utf-8') as pf: try: preset_data = json.load(pf) except ValueError: continue for preset in preset_data.keys(): pd = PresetDefaults(preset, opts=SoSOptions()) data = preset_data[preset] pd.desc = data[DESC] if DESC in data else "" pd.note = data[NOTE] if NOTE in data else "" if OPTS in data: for arg in data[OPTS]: setattr(pd.opts, arg, data[OPTS][arg]) pd.builtin = False self.presets[preset] = pd def add_preset(self, name=None, desc=None, note=None, opts=SoSOptions()): """Add a new on-disk preset and write it to the configured presets path. :param preset: the new PresetDefaults to add """ presets_path = self.presets_path if not name: raise ValueError("Preset name cannot be empty") if name in self.presets.keys(): raise ValueError(f"A preset with name '{name}' already exists") preset = PresetDefaults(name=name, desc=desc, note=note, opts=opts) preset.builtin = False self.presets[preset.name] = preset preset.write(presets_path) def del_preset(self, name=""): if not name or name not in self.presets.keys(): raise ValueError(f"Unknown profile: '{name}'") preset = self.presets[name] if preset.builtin: raise ValueError(f"Cannot delete built-in preset '{preset.name}'") preset.delete(self.presets_path) self.presets.pop(name) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/package_managers/0000775000175000017500000000000014660147624016652 5ustar arifarifsos-4.8.0/sos/policies/package_managers/flatpak.py0000664000175000017500000000164214660147624020651 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jose Castillo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.package_managers import PackageManager class FlatpakPackageManager(PackageManager): """Package Manager for Flatpak distributions """ query_command = 'flatpak list --columns=name,version,branch' query_path_command = '' files_command = '' verify_command = '' verify_filter = '' def _parse_pkg_list(self, pkg_list): for line in pkg_list.splitlines(): pkg = line.split("\t") yield (pkg[0], pkg[1], pkg[2]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/package_managers/__init__.py0000664000175000017500000003346314660147624020774 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re import fnmatch from sos.utilities import sos_get_command_output class PackageManager(): """Encapsulates a package manager. If you provide a query_command to the constructor it should print each package on the system in the following format:: package name|package.version You may also subclass this class and provide a _generate_pkg_list method to build the list of packages and versions. :cvar query_command: The command to use for querying packages :vartype query_command: ``str`` or ``None`` :cvar verify_command: The command to use for verifying packages :vartype verify_command: ``str`` or ``None`` :cvar verify_filter: Optional filter to use for controlling package verification :vartype verify_filter: ``str or ``None`` :cvar files_command: The command to use for getting file lists for packages :vartype files_command: ``str`` or ``None`` :cvar chroot: Perform a chroot when executing `files_command` :vartype chroot: ``bool`` :cvar remote_exec: If package manager is on a remote system (e.g. for sos collect), use this to execute commands :vartype remote_exec: ``SoSTransport.run_command()`` """ query_command = None verify_command = None verify_filter = None files_command = None query_path_command = None chroot = None files = None def __init__(self, chroot=None, remote_exec=None): self._packages = None self.files = [] self.remote_exec = remote_exec if chroot: self.chroot = chroot @property def packages(self): if self._packages is None: self._generate_pkg_list() return self._packages @property def manager_name(self): return self.__class__.__name__.lower().split('package', maxsplit=1)[0] def exec_cmd(self, command, timeout=30, need_root=False, env=None, use_shell=False, chroot=None): """ Runs a package manager command, either via sos_get_command_output() if local, or via a SoSTransport's run_command() if this needs to be run remotely, as in the case of remote nodes for use during `sos collect`. :param command: The command to execute :type command: ``str`` :param timeout: Timeout for command to run, in seconds :type timeout: ``int`` :param need_root: Does the command require root privileges? :type need_root: ``bool`` :param env: Environment variables to set :type env: ``dict`` with keys being env vars to define :param use_shell: If running remotely, does the command require obtaining a shell? :type use_shell: ``bool`` :param chroot: If necessary, chroot command execution to here :type chroot: ``None`` or ``str`` :returns: The output of the command :rtype: ``str`` """ if self.remote_exec: ret = self.remote_exec(command, timeout, need_root, env, use_shell) else: ret = sos_get_command_output(command, timeout, chroot=chroot, env=env) if ret['status'] == 0: return ret['output'] # In the case of package managers, we don't want to potentially iterate # over stderr, so prevent the package methods from doing anything at # all by returning nothing. return '' def all_pkgs_by_name(self, name): """ Get a list of packages that match name. :param name: The name of the package :type name: ``str`` :returns: List of all packages matching `name` :rtype: ``list`` """ return fnmatch.filter(self.packages.keys(), name) def all_pkgs_by_name_regex(self, regex_name, flags=0): """ Get a list of packages that match regex_name. :param regex_name: The regex to use for matching package names against :type regex_name: ``str`` :param flags: Flags for the `re` module when matching `regex_name` :returns: All packages matching `regex_name` :rtype: ``list`` """ reg = re.compile(regex_name, flags) return [pkg for pkg in self.packages.keys() if reg.match(pkg)] def pkg_by_name(self, name): """ Get a single package that matches name. :param name: The name of the package :type name: ``str`` :returns: The first package that matches `name` :rtype: ``str`` """ try: return self.packages[name] except Exception: return None def _parse_pkg_list(self, pkg_list): """ Using the output of `query_command`, build the _packages dict. This should be overridden by distinct package managers and be a generator for _generate_pkg_list which will insert the packages into the _packages dict. This method should yield a tuple of name, version, release for each package parsed. If the package manager or distribution does not use a release field, set it to None. :param pkg_list: The output of the result of `query_command` :type pkg_list: ``str`` """ raise NotImplementedError def _generate_pkg_list(self): """Generates a dictionary of packages for internal use by the package manager in the format:: {'package_name': {'name': 'package_name', 'version': 'major.minor.version', 'release': 'package release' or None, 'pkg_manager': 'package manager name'}} """ if self._packages is None: self._packages = {} if self.query_command: cmd = self.query_command pkg_list = self.exec_cmd(cmd, timeout=30, chroot=self.chroot) for pkg in self._parse_pkg_list(pkg_list): self._packages[pkg[0]] = { 'name': pkg[0], 'version': pkg[1].split('.'), 'release': pkg[2], 'pkg_manager': self.manager_name } def pkg_version(self, pkg): """Returns the entry in self.packages for pkg if it exists :param pkg: The name of the package :type pkg: ``str`` :returns: Package name and version, if package exists :rtype: ``dict`` if found, else ``None`` """ if pkg in self.packages: return self.packages[pkg] return None def all_files(self): """ Get a list of files known by the package manager :returns: All files known by the package manager :rtype: ``list`` """ if self.files_command and not self.files: cmd = self.files_command files = self.exec_cmd(cmd, timeout=180, chroot=self.chroot) self.files = files.splitlines() return self.files def pkg_by_path(self, path): """Given a path, return the package that owns that path. :param path: The filepath to check for package ownership :type path: ``str`` :returns: The package name or 'unknown' :rtype: ``str`` """ if not self.query_path_command: return 'unknown' try: cmd = f"{self.query_path_command} {path}" pkg = self.exec_cmd(cmd, timeout=5, chroot=self.chroot) return pkg.splitlines() or 'unknown' except Exception: return 'unknown' def build_verify_command(self, packages): """build_verify_command(self, packages) -> str Generate a command to verify the list of packages given in ``packages`` using the native package manager's verification tool. The command to be executed is returned as a string that may be passed to a command execution routine (for e.g. ``sos_get_command_output()``. :param packages: a string, or a list of strings giving package names to be verified. :returns: a string containing an executable command that will perform verification of the given packages. :rtype: str or ``NoneType`` """ if not self.verify_command: return None # The re.match(pkg) used by all_pkgs_by_name_regex() may return # an empty list (`[[]]`) when no package matches: avoid building # an rpm -V command line with the empty string as the package # list in this case. by_regex = self.all_pkgs_by_name_regex verify_list = filter(None, map(by_regex, packages)) # No packages after regex match? if not verify_list: return None verify_packages = "" for package_list in verify_list: for package in package_list: if any(f in package for f in self.verify_filter): continue if verify_packages: verify_packages += " " verify_packages += package return self.verify_command + " " + verify_packages class MultiPackageManager(PackageManager): """ This class is used to leverage multiple individual package managers as a single entity on systems that support multiple concurrent package managers. Policies that use this approach will need to specify a primary package manager, and at least one fallback manager. When queries are sent to this manager, the primary child manager is checked first. If there is a valid, not None, response (e.g. a given package is installed) then that response is used. However, if the response is empty or None, the fallback managers are then queried in the order they were passed to MultiPackageManager during initialization. :param primary: The primary package manager to rely on :type primary: A subclass of `PackageManager` :param fallbacks: A list of package managers to use if the primary does not provide a response :type fallbacks: ``list`` of `PackageManager` subclasses """ def __init__(self, primary, fallbacks, chroot=None, remote_exec=None): super().__init__(chroot=chroot, remote_exec=remote_exec) if not issubclass(primary, PackageManager): raise Exception( f"Primary package manager must be PackageManager subclass, not" f" {primary.__class__}" ) if not isinstance(fallbacks, list): raise Exception('Fallbacks must be specified in a list') for pm in fallbacks: if not issubclass(pm, PackageManager): raise Exception( f"Fallback package managers must be PackageManager " f"subclass, not {pm.__class__}" ) self.primary = primary(chroot=chroot, remote_exec=remote_exec) self.fallbacks = [ pm(chroot=chroot, remote_exec=remote_exec) for pm in fallbacks ] if not self.fallbacks: raise Exception( 'Must define at least one fallback package manager' ) self._managers = [self.primary] self._managers.extend(self.fallbacks) def all_files(self): if not self.files: for pm in self._managers: self.files.extend(pm.all_files()) return self.files def _generate_pkg_list(self): if self._packages is None: self._packages = {} self._packages.update(self.primary.packages) for pm in self.fallbacks: _pkgs = pm.packages for pkg in _pkgs.keys(): if pkg not in self._packages: self._packages[pkg] = _pkgs[pkg] def _pm_wrapper(self, method): """ This wrapper method is used to provide implicit iteration through the primary and any defined fallback managers that are set for a given instance of MultiPackageManager. Important note: we pass the _name_ of the method to run here as a string, and not any actual method as we rely on iteratively looking up the actual method in each package manager. :param method: The name of the method we're wrapping for the purpose of iterating through defined package managers :type method: ``str`` """ def pkg_func(*args, **kwargs): ret = None for pm in self._managers: if not ret or ret == 'unknown': _wrapped_func = getattr(pm, method) ret = _wrapped_func(*args, **kwargs) return ret return pkg_func def __getattribute__(self, item): # if the attr is callable, then we need to iterate over our child # managers until we get a response, unless it is _generate_pkg_list in # which case we only want to use the one actually defined here, or # _pm_wrapper, which we need to avoid this override for to not hit # recursion hell. if item in ['_generate_pkg_list', '_pm_wrapper', 'all_files']: return super().__getattribute__(item) attr = super().__getattribute__(item) if hasattr(attr, '__call__'): return self._pm_wrapper(item) return attr # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/package_managers/snap.py0000664000175000017500000000201514660147624020163 0ustar arifarif# Copyright 2023 Canonical Ltd. Arif Ali # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.package_managers import PackageManager class SnapPackageManager(PackageManager): """Subclass for snap-based distributions """ query_command = "snap list" query_path_command = "" verify_command = "" verify_filter = "" def _parse_pkg_list(self, pkg_list): for line in pkg_list.splitlines(): if line == "": continue pkg = line.split() if pkg[0] == "Name" or pkg[0] == "Connection": continue name, version = pkg[0], pkg[1] yield (name, version, None) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/package_managers/dpkg.py0000664000175000017500000000204414660147624020151 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.package_managers import PackageManager class DpkgPackageManager(PackageManager): """Subclass for dpkg-based distrubitons """ query_command = "dpkg-query -W -f='${Package}|${Version}|${Status}\\n'" query_path_command = "dpkg -S" verify_command = "dpkg --verify" verify_filter = "" def _parse_pkg_list(self, pkg_list): for pkg in pkg_list.splitlines(): if '|' not in pkg: continue name, version, status = pkg.split('|') if 'deinstall' in status: continue yield (name, version, None) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/package_managers/rpm.py0000664000175000017500000000203714660147624020024 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.package_managers import PackageManager class RpmPackageManager(PackageManager): """Package Manager for RPM-based distributions """ query_command = 'rpm -qa --queryformat "%{NAME}|%{VERSION}|%{RELEASE}\\n"' query_path_command = 'rpm -qf' files_command = 'rpm -qal' verify_command = 'rpm -V' verify_filter = ["debuginfo", "-devel"] def _parse_pkg_list(self, pkg_list): for pkg in pkg_list.splitlines(): if '|' not in pkg: continue name, version, release = pkg.split('|') yield (name, version, release) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/init_systems/0000775000175000017500000000000014660147624016134 5ustar arifarifsos-4.8.0/sos/policies/init_systems/__init__.py0000664000175000017500000001414114660147624020246 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.utilities import sos_get_command_output class InitSystem(): """Encapsulates an init system to provide service-oriented functions to sos. This should be used to query the status of services, such as if they are enabled or disabled on boot, or if the service is currently running. :param init_cmd: The binary used to interact with the init system :type init_cmd: ``str`` :param list_cmd: The list subcmd given to `init_cmd` to list services :type list_cmd: ``str`` :param query_cmd: The query subcmd given to `query_cmd` to query the status of services :type query_cmd: ``str`` :param chroot: Location to chroot to for any command execution, i.e. the sysroot if we're running in a container :type chroot: ``str`` or ``None`` """ def __init__(self, init_cmd=None, list_cmd=None, query_cmd=None, chroot=None): """Initialize a new InitSystem()""" self.services = {} self.init_cmd = init_cmd self.list_cmd = f"{self.init_cmd} {list_cmd}" or None self.query_cmd = f"{self.init_cmd} {query_cmd}" or None self.chroot = chroot def is_enabled(self, name): """Check if given service name is enabled :param name: The name of the service :type name: ``str`` :returns: ``True`` if the service is enabled, else ``False`` :rtype: ``bool`` """ if self.services and name in self.services: return self.services[name]['config'] == 'enabled' return False def is_disabled(self, name): """Check if a given service name is disabled :param name: The name of the service :type name: ``str`` :returns: ``True`` if the service is disabled, else ``False`` :rtype: ``bool`` """ if self.services and name in self.services: return self.services[name]['config'] == 'disabled' return False def is_service(self, name): """Checks if the given service name exists on the system at all, this does not check for the service status :param name: The name of the service :type name: ``str`` :returns: ``True`` if the service exists, else ``False`` :rtype: ``bool`` """ return name in self.services # pylint: disable=unused-argument def is_running(self, name, default=True): """Checks if the given service name is in a running state. This should be overridden by initsystems that subclass InitSystem :param name: The name of the service :type name: ``str`` :param default: The default response in case the check fails :type default: ``bool` :returns: ``True`` if the service is running, else ``default`` :rtype: ``bool`` """ # This is going to be primarily used in gating if service related # commands are going to be run or not. Default to always returning # True when an actual init system is not specified by policy so that # we don't inadvertantly restrict sos reports on those systems return default def load_all_services(self): """This loads all services known to the init system into a dict. The dict should be keyed by the service name, and contain a dict of the name and service status This must be overridden by anything that subclasses `InitSystem` in order for service methods to function properly """ pass def _query_service(self, name): """Query an individual service""" if self.query_cmd: try: return sos_get_command_output( f"{self.query_cmd} {name}", chroot=self.chroot ) except Exception: return None return None def parse_query(self, output): """Parses the output returned by the query command to make a determination of what the state of the service is This should be overriden by anything that subclasses InitSystem :param output: The raw output from querying the service with the configured `query_cmd` :type output: ``str`` :returns: A state for the service, e.g. 'active', 'disabled', etc... :rtype: ``str`` """ return output def get_service_names(self, regex): """Get a list of all services discovered on the system that match the given regex. :param regex: The service name regex to match against :type regex: ``str`` """ reg = re.compile(regex, re.I) return [s for s in self.services.keys() if reg.match(s)] def get_service_status(self, name): """Get the status for the given service name along with the output of the query command :param name: The name of the service :type name: ``str`` :returns: Service status and query_cmd output from the init system :rtype: ``dict`` with keys `name`, `status`, and `output` """ _default = { 'name': name, 'status': 'missing', 'output': '' } if name not in self.services: return _default if 'status' in self.services[name]: # service status has been queried before, return existing info return self.services[name] svc = self._query_service(name) if svc is not None: self.services[name]['status'] = self.parse_query(svc['output']) self.services[name]['output'] = svc['output'] return self.services[name] return _default # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/init_systems/systemd.py0000664000175000017500000000333614660147624020203 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.init_systems import InitSystem from sos.utilities import shell_out class SystemdInit(InitSystem): """InitSystem abstraction for SystemD systems""" def __init__(self, chroot=None): super().__init__( init_cmd='systemctl', list_cmd='list-unit-files --type=service', query_cmd='status', chroot=chroot ) self.load_all_services() def parse_query(self, output): for line in output.splitlines(): if line.strip().startswith('Active:'): return line.split()[1] return 'unknown' def load_all_services(self): svcs = shell_out(self.list_cmd, chroot=self.chroot).splitlines()[1:] for line in svcs: try: name = line.split('.service')[0] config = line.split()[1] self.services[name] = { 'name': name, 'config': config } except IndexError: # not a valid line to extract status info from pass def is_running(self, name, default=False): try: svc = self.get_service_status(name) return svc['status'] == 'active' except Exception: return default # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/0000775000175000017500000000000014660147624015071 5ustar arifarifsos-4.8.0/sos/policies/distros/amazon.py0000664000175000017500000000244514660147624016735 0ustar arifarif# Copyright (C) Red Hat, Inc. 2019 # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class AmazonPolicy(RedHatPolicy): distro = "Amazon Linux" vendor = "Amazon" vendor_urls = [('Distribution Website', 'https://aws.amazon.com')] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.exists(OS_RELEASE): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'Amazon Linux' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/uniontechserver.py0000664000175000017500000000244314660147624020671 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class UnionTechPolicy(RedHatPolicy): distro = "UnionTech OS Server" vendor = "The UnionTech Project" vendor_urls = [('Distribution Website', 'https://www.chinauos.com/')] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.exists(OS_RELEASE): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'UnionTech OS Server' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/azure.py0000664000175000017500000000304114660147624016567 0ustar arifarif# Copyright (C) Eric Desrochers # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import AzurePlugin from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class AzurePolicy(RedHatPolicy): distro = "Azure Linux" vendor = "Microsoft" vendor_urls = [ ('Distribution Website', 'https://github.com/microsoft/azurelinux') ] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.valid_subclasses += [AzurePlugin] @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.exists(OS_RELEASE): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'Common Base Linux Mariner' in line: return True if 'Microsoft Azure Linux' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/openeuler.py0000664000175000017500000000255714660147624017452 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import OpenEulerPlugin from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class OpenEulerPolicy(RedHatPolicy): distro = "openEuler" vendor = "The openEuler Project" vendor_urls = [('Distribution Website', 'https://openeuler.org/')] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.valid_subclasses += [OpenEulerPlugin] @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.exists(OS_RELEASE): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'openEuler' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/circle.py0000664000175000017500000000300014660147624016675 0ustar arifarif# Copyright (C) Bella Zhang # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class CirclePolicy(RedHatPolicy): distro = "Circle Linux" vendor = "The Circle Linux Project" vendor_urls = [('Distribution Website', 'https://cclinux.org')] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote # Return False if /etc/os-release is missing if not os.path.exists(OS_RELEASE): return False # Return False if /etc/circle-release is missing if not os.path.isfile('/etc/circle-release'): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'Circle Linux' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/__init__.py0000664000175000017500000011620114660147624017203 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # pylint: disable=too-many-branches import os import re from getpass import getpass from sos import _sos as _ from sos.policies import Policy from sos.policies.init_systems import InitSystem from sos.policies.init_systems.systemd import SystemdInit from sos.policies.runtimes.crio import CrioContainerRuntime from sos.policies.runtimes.podman import PodmanContainerRuntime from sos.policies.runtimes.docker import DockerContainerRuntime from sos.policies.runtimes.lxd import LxdContainerRuntime from sos.utilities import (shell_out, is_executable, bold, sos_get_command_output, TIMEOUT_DEFAULT) try: import requests REQUESTS_LOADED = True except ImportError: REQUESTS_LOADED = False try: import boto3 BOTO3_LOADED = True except ImportError: BOTO3_LOADED = False # Container environment variables for detecting if we're in a container ENV_CONTAINER = 'container' ENV_HOST_SYSROOT = 'HOST' class LinuxPolicy(Policy): """This policy is meant to be an abc class that provides common implementations used in Linux distros""" distro = "Linux" vendor = "None" PATH = "/bin:/sbin:/usr/bin:/usr/sbin" init = None # _ prefixed class attrs are used for storing any vendor-defined defaults # the non-prefixed attrs are used by the upload methods, and will be set # to the cmdline/config file values, if provided. If not provided, then # those attrs will be set to the _ prefixed values as a fallback. # TL;DR Use _upload_* for policy default values, use upload_* when wanting # to actual use the value in a method/override _upload_url = None _upload_directory = '/' _upload_user = None _upload_password = None _upload_method = None _upload_s3_endpoint = 'https://s3.amazonaws.com' _upload_s3_bucket = None _upload_s3_access_key = None _upload_s3_secret_key = None _upload_s3_region = None _upload_s3_object_prefix = '' default_container_runtime = 'docker' _preferred_hash_name = None upload_url = None upload_user = None upload_password = None upload_s3_endpoint = None upload_s3_bucket = None upload_s3_access_key = None upload_s3_secret_key = None upload_s3_region = None upload_s3_object_prefix = None # collector-focused class attrs containerized = False container_image = None sos_path_strip = None sos_pkg_name = None sos_bin_path = '/usr/bin' sos_container_name = 'sos-collector-tmp' container_version_command = None container_authfile = None def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, probe_runtime=probe_runtime, remote_exec=remote_exec) if sysroot: self.sysroot = sysroot else: self.sysroot = self._container_init() or '/' self.init_kernel_modules() if init is not None: self.init_system = init elif os.path.isdir("/run/systemd/system/"): self.init_system = SystemdInit(chroot=self.sysroot) else: self.init_system = InitSystem() self.runtimes = {} if self.probe_runtime: _crun = [ PodmanContainerRuntime(policy=self), DockerContainerRuntime(policy=self), CrioContainerRuntime(policy=self), LxdContainerRuntime(policy=self), ] for runtime in _crun: if runtime.check_is_active(): self.runtimes[runtime.name] = runtime if runtime.name == self.default_container_runtime: self.runtimes['default'] = self.runtimes[runtime.name] self.runtimes[runtime.name].load_container_info() if self.runtimes and 'default' not in self.runtimes.keys(): # still allow plugins to query a runtime present on the system # even if that is not the policy default one idx = list(self.runtimes.keys()) self.runtimes['default'] = self.runtimes[idx[0]] @classmethod def set_forbidden_paths(cls): return [ '/etc/passwd', '/etc/shadow' ] def kernel_version(self): return self.release def host_name(self): return self.hostname def is_kernel_smp(self): return self.smp def get_arch(self): return self.machine def get_local_name(self): """Returns the name usd in the pre_work step""" return self.host_name() def sanitize_filename(self, name): return re.sub(r"[^-a-z,A-Z.0-9]", "", name) @classmethod def display_help(cls, section): if cls == LinuxPolicy: cls.display_self_help(section) else: section.set_title(f"{cls.distro} Distribution Policy") cls.display_distro_help(section) @classmethod def display_self_help(cls, section): section.set_title("SoS Distribution Policies") section.add_text( 'Distributions supported by SoS will each have a specific policy ' 'defined for them, to ensure proper operation of SoS on those ' 'systems.' ) @classmethod def display_distro_help(cls, section): if cls.__doc__ and cls.__doc__ is not LinuxPolicy.__doc__: section.add_text(cls.__doc__) else: section.add_text( '\nDetailed help information for this policy is not available' ) # instantiate the requested policy so we can report more interesting # information like $PATH and loaded presets _pol = cls(None, None, False) section.add_text( f"Default --upload location: {_pol._upload_url}" ) section.add_text( f"Default container runtime: {_pol.default_container_runtime}", newline=False ) section.add_text( f"$PATH used when running report: {_pol.PATH}", newline=False ) refsec = section.add_section('Reference URLs') for url in cls.vendor_urls: refsec.add_text(f"{' ':>8}{url[0]:<30}{url[1]:<40}", newline=False) presec = section.add_section('Presets Available With This Policy\n') presec.add_text( bold( f"{' ':>8}{'Preset Name':<20}{'Description':<45}" f"{'Enabled Options':<30}" ), newline=False ) for preset, value in _pol.presets.items(): _opts = ' '.join(value.opts.to_args()) presec.add_text( f"{' ':>8}{preset:<20}{value.desc:<45}{_opts:<30}", newline=False ) def _container_init(self): """Check if sos is running in a container and perform container specific initialisation based on ENV_HOST_SYSROOT. """ if ENV_CONTAINER in os.environ: if os.environ[ENV_CONTAINER] in ['docker', 'oci', 'podman']: self._in_container = True if ENV_HOST_SYSROOT in os.environ: if not os.environ[ENV_HOST_SYSROOT]: # guard against blank/improperly unset values return None self._tmp_dir = os.path.abspath( os.environ[ENV_HOST_SYSROOT] + self._tmp_dir ) return os.environ[ENV_HOST_SYSROOT] return None def init_kernel_modules(self): """Obtain a list of loaded kernel modules to reference later for plugin enablement and SoSPredicate checks """ self.kernel_mods = [] release = os.uname().release # first load modules from lsmod lines = shell_out("lsmod", timeout=0, chroot=self.sysroot).splitlines() self.kernel_mods.extend([ line.split()[0].strip() for line in lines[1:] ]) # next, include kernel builtins builtins = self.join_sysroot( f"/usr/lib/modules/{release}/modules.builtin" ) try: with open(builtins, "r", encoding='utf-8') as mfile: for line in mfile: kmod = line.split('/')[-1].split('.ko')[0] self.kernel_mods.append(kmod) except IOError as err: self.soslog.warning(f"Unable to read kernel builtins: {err}") # finally, parse kconfig looking for specific kconfig strings that # have been verified to not appear in either lsmod or modules.builtin # regardless of how they are built config_strings = { 'devlink': 'CONFIG_NET_DEVLINK', 'dm_mod': 'CONFIG_BLK_DEV_DM' } kconfigs = ( f"/boot/config-{release}", f"/lib/modules/{release}/config", ) for kconfig in kconfigs: kconfig = self.join_sysroot(kconfig) if os.path.exists(kconfig): booted_config = kconfig break else: self.soslog.warning("Unable to find booted kernel config") return kconfigs = [] try: with open(booted_config, "r", encoding='utf-8') as kfile: for line in kfile: if '=y' in line: kconfigs.append(line.split('=y')[0]) except IOError as err: self.soslog.warning(f"Unable to read booted kernel config: {err}") for builtin, value in config_strings.items(): if value in kconfigs: self.kernel_mods.append(builtin) def join_sysroot(self, path): if self.sysroot and self.sysroot != '/': path = os.path.join(self.sysroot, path.lstrip('/')) return path def pre_work(self): # this method will be called before the gathering begins cmdline_opts = self.commons['cmdlineopts'] caseid = cmdline_opts.case_id if cmdline_opts.case_id else "" if cmdline_opts.low_priority: self._configure_low_priority() # Set the cmdline settings to the class attrs that are referenced later # The policy default '_' prefixed versions of these are untouched to # allow fallback self.upload_url = cmdline_opts.upload_url self.upload_user = cmdline_opts.upload_user self.upload_directory = cmdline_opts.upload_directory self.upload_password = cmdline_opts.upload_pass self.upload_archive_name = '' self.upload_s3_endpoint = cmdline_opts.upload_s3_endpoint self.upload_s3_region = cmdline_opts.upload_s3_region self.upload_s3_access_key = cmdline_opts.upload_s3_access_key self.upload_s3_bucket = cmdline_opts.upload_s3_bucket self.upload_s3_object_prefix = cmdline_opts.upload_s3_object_prefix self.upload_s3_secret_key = cmdline_opts.upload_s3_secret_key # set or query for case id if not cmdline_opts.batch and not \ cmdline_opts.quiet: if caseid: self.commons['cmdlineopts'].case_id = caseid else: self.commons['cmdlineopts'].case_id = input( _("Optionally, please enter the case id that you are " "generating this report for [%s]: ") % caseid ) if cmdline_opts.case_id: self.case_id = cmdline_opts.case_id # set or query for upload credentials; this needs to be done after # setting case id, as below methods might rely on detection of it if not cmdline_opts.batch and not \ cmdline_opts.quiet: # Policies will need to handle the prompts for user information if cmdline_opts.upload and self.get_upload_url() and \ not cmdline_opts.upload_protocol == 's3': self.prompt_for_upload_user() self.prompt_for_upload_password() elif cmdline_opts.upload_protocol == 's3': self.prompt_for_upload_s3_bucket() self.prompt_for_upload_s3_endpoint() self.prompt_for_upload_s3_access_key() self.prompt_for_upload_s3_secret_key() self.ui_log.info('') def _configure_low_priority(self): """Used to constrain sos to a 'low priority' execution, potentially letting individual policies set their own definition of what that is. By default, this will attempt to assign sos to an idle io class via ionice if available. We will also renice our own pid to 19 in order to not cause competition with other host processes for CPU time. """ _pid = os.getpid() if is_executable('ionice'): ret = sos_get_command_output( f"ionice -c3 -p {_pid}", timeout=5 ) if ret['status'] == 0: self.soslog.info('Set IO class to idle') else: msg = (f"Error setting IO class to idle: {ret['output']} " f"(exit code {ret['status']})") self.soslog.error(msg) else: self.ui_log.warning( "Warning: unable to constrain report to idle IO class: " "ionice is not available." ) try: os.nice(20) self.soslog.info('Set niceness of report to 19') except Exception as err: self.soslog.error(f"Error setting report niceness to 19: {err}") def prompt_for_upload_s3_access_key(self): """Should be overridden by policies to determine if an access key needs to be provided for upload or not """ if not self.get_upload_s3_access_key(): msg = ( "Please provide the upload access key for bucket" f" {self.get_upload_s3_bucket()} via endpoint" f" {self.get_upload_s3_endpoint()}: " ) self.upload_s3_access_key = input(_(msg)) def prompt_for_upload_s3_secret_key(self): """Should be overridden by policies to determine if a secret key needs to be provided for upload or not """ if not self.get_upload_s3_secret_key(): msg = ( "Please provide the upload secret key for bucket" f" {self.get_upload_s3_bucket()} via endpoint" f" {self.get_upload_s3_endpoint()}: " ) self.upload_s3_secret_key = getpass(msg) def prompt_for_upload_s3_bucket(self): """Should be overridden by policies to determine if a bucket needs to be provided for upload or not """ if not self.upload_s3_bucket: if self.upload_url and self.upload_url.startswith('s3://'): self.upload_s3_bucket = self.upload_url[5:] else: user_input = input(_("Please provide the upload bucket: ")) self.upload_s3_bucket = user_input.strip('/') return self.upload_s3_bucket def prompt_for_upload_s3_endpoint(self): """Should be overridden by policies to determine if an endpoint needs to be provided for upload or not """ default_endpoint = self._upload_s3_endpoint if not self.upload_s3_endpoint: msg = ( "Please provide the upload endpoint for bucket" f" {self.get_upload_s3_bucket()}" f" (default: {default_endpoint}): " ) user_input = input(_(msg)) self.upload_s3_endpoint = user_input or default_endpoint return self.upload_s3_endpoint def prompt_for_upload_user(self): """Should be overridden by policies to determine if a user needs to be provided or not """ if not self.get_upload_user(): msg = f"Please provide upload user for {self.get_upload_url()}: " self.upload_user = input(_(msg)) def prompt_for_upload_password(self): """Should be overridden by policies to determine if a password needs to be provided for upload or not """ if not self.get_upload_password() and (self.get_upload_user() != self._upload_user): msg = ("Please provide the upload password for " f"{self.get_upload_user()}: ") self.upload_password = getpass(msg) def upload_archive(self, archive): """ Entry point for sos attempts to upload the generated archive to a policy or user specified location. Currently there is support for HTTPS, SFTP, and FTP. HTTPS uploads are preferred for policy-defined defaults. Policies that need to override uploading methods should override the respective upload_https(), upload_sftp(), and/or upload_ftp() methods and should NOT override this method. :param archive: The archive filepath to use for upload :type archive: ``str`` In order to enable this for a policy, that policy needs to implement the following: Required Class Attrs :_upload_url: The default location to use. Note these MUST include protocol header :_upload_user: Default username, if any else None :_upload_password: Default password, if any else None The following Class Attrs may optionally be overidden by the Policy :_upload_directory: Default FTP server directory, if any The following methods may be overridden by ``Policy`` as needed `prompt_for_upload_user()` Determines if sos should prompt for a username or not. `get_upload_user()` Determines if the default or a different username should be used `get_upload_https_auth()` Format authentication data for HTTPS uploads `get_upload_url_string()` Print a more human-friendly string than vendor URLs """ self.upload_archive_name = archive if not self.upload_url: self.upload_url = self.get_upload_url() if not self.upload_url: raise Exception("No upload destination provided by policy or by " "--upload-url") upload_func = self._determine_upload_type() self.ui_log.info( _(f"Attempting upload to {self.get_upload_url_string()}") ) return upload_func() def _determine_upload_type(self): """Based on the url provided, determine what type of upload to attempt. Note that this requires users to provide a FQDN address, such as https://myvendor.com/api or ftp://myvendor.com instead of myvendor.com/api or myvendor.com """ prots = { 'ftp': self.upload_ftp, 'sftp': self.upload_sftp, 'https': self.upload_https, 's3': self.upload_s3 } if self.commons['cmdlineopts'].upload_protocol in prots.keys(): return prots[self.commons['cmdlineopts'].upload_protocol] if '://' not in self.upload_url: raise Exception("Must provide protocol in upload URL") prot, _ = self.upload_url.split('://') if prot not in prots.keys(): raise Exception(f"Unsupported or unrecognized protocol: {prot}") return prots[prot] def get_upload_https_auth(self, user=None, password=None): """Formats the user/password credentials using basic auth :param user: The username for upload :type user: ``str`` :param password: Password for `user` to use for upload :type password: ``str`` :returns: The user/password auth suitable for use in requests calls :rtype: ``requests.auth.HTTPBasicAuth()`` """ if not user: user = self.get_upload_user() if not password: password = self.get_upload_password() return requests.auth.HTTPBasicAuth(user, password) def get_upload_s3_access_key(self): """Helper function to determine if we should use the policy default upload access key or one provided by the user :returns: The access_key to use for upload :rtype: ``str`` """ return (os.getenv('SOSUPLOADS3ACCESSKEY', None) or self.upload_s3_access_key or self._upload_s3_access_key) def get_upload_s3_endpoint(self): """Helper function to determine if we should use the policy default upload endpoint or one provided by the user :returns: The S3 Endpoint to use for upload :rtype: ``str`` """ if not self.upload_s3_endpoint: self.prompt_for_upload_s3_endpoint() return self.upload_s3_endpoint def get_upload_s3_region(self): """Helper function to determine if we should use the policy default upload region or one provided by the user :returns: The S3 region to use for upload :rtype: ``str`` """ return self.upload_s3_region or self._upload_s3_region def get_upload_s3_bucket(self): """Helper function to determine if we should use the policy default upload bucket or one provided by the user :returns: The S3 bucket to use for upload :rtype: ``str`` """ if self.upload_url and self.upload_url.startswith('s3://'): bucket_and_prefix = self.upload_url[5:].split('/', 1) self.upload_s3_bucket = bucket_and_prefix[0] if len(bucket_and_prefix) > 1: self.upload_s3_object_prefix = bucket_and_prefix[1] if not self.upload_s3_bucket: self.prompt_for_upload_s3_bucket() return self.upload_s3_bucket or self._upload_s3_bucket def get_upload_s3_object_prefix(self): """Helper function to determine if we should use the policy default upload object prefix or one provided by the user :returns: The S3 object prefix to use for upload :rtype: ``str`` """ return self.upload_s3_object_prefix or self._upload_s3_object_prefix def get_upload_s3_secret_key(self): """Helper function to determine if we should use the policy default upload secret key or one provided by the user :returns: The S3 secret key to use for upload :rtype: ``str`` """ return (os.getenv('SOSUPLOADS3SECRETKEY', None) or self.upload_s3_secret_key or self._upload_s3_secret_key) def get_upload_url(self): """Helper function to determine if we should use the policy default upload url or one provided by the user :returns: The URL to use for upload :rtype: ``str`` """ if not self.upload_url and ( self.upload_s3_bucket and self.upload_s3_access_key and self.upload_s3_secret_key ): bucket = self.get_upload_s3_bucket() prefix = self.get_upload_s3_object_prefix() self._upload_url = f"s3://{bucket}/{prefix}" return self.upload_url or self._upload_url def get_upload_url_string(self): """Used by distro policies to potentially change the string used to report upload location from the URL to a more human-friendly string """ return self.get_upload_url() def get_upload_user(self): """Helper function to determine if we should use the policy default upload user or one provided by the user :returns: The username to use for upload :rtype: ``str`` """ return (os.getenv('SOSUPLOADUSER', None) or self.upload_user or self._upload_user) def get_upload_password(self): """Helper function to determine if we should use the policy default upload password or one provided by the user A user provided password, either via option or the 'SOSUPLOADPASSWORD' environment variable will have precendent over any policy value :returns: The password to use for upload :rtype: ``str`` """ return (os.getenv('SOSUPLOADPASSWORD', None) or self.upload_password or self._upload_password) def upload_sftp(self, user=None, password=None): """Attempts to upload the archive to an SFTP location. Due to the lack of well maintained, secure, and generally widespread python libraries for SFTP, sos will shell-out to the system's local ssh installation in order to handle these uploads. Do not override this method with one that uses python-paramiko, as the upstream sos team will reject any PR that includes that dependency. """ # if we somehow don't have sftp available locally, fail early if not is_executable('sftp'): raise Exception('SFTP is not locally supported') # soft dependency on python3-pexpect, which we need to use to control # sftp login since as of this writing we don't have a viable solution # via ssh python bindings commonly available among downstreams try: import pexpect except ImportError: raise Exception('SFTP upload requires python3-pexpect, which is ' 'not currently installed') sftp_connected = False if not user: user = self.get_upload_user() if not password: password = self.get_upload_password() # need to strip the protocol prefix here sftp_url = self.get_upload_url().replace('sftp://', '') sftp_cmd = f"sftp -oStrictHostKeyChecking=no {user}@{sftp_url}" ret = pexpect.spawn(sftp_cmd, encoding='utf-8') sftp_expects = [ 'sftp>', 'password:', 'Connection refused', pexpect.TIMEOUT, pexpect.EOF ] idx = ret.expect(sftp_expects, timeout=15) if idx == 0: sftp_connected = True elif idx == 1: ret.sendline(password) pass_expects = [ 'sftp>', 'Permission denied', pexpect.TIMEOUT, pexpect.EOF ] sftp_connected = ret.expect(pass_expects, timeout=10) == 0 if not sftp_connected: ret.close() raise Exception("Incorrect username or password for " f"{self.get_upload_url_string()}") elif idx == 2: raise Exception("Connection refused by " f"{self.get_upload_url_string()}. Incorrect port?") elif idx == 3: raise Exception("Timeout hit trying to connect to " f"{self.get_upload_url_string()}") elif idx == 4: raise Exception("Unexpected error trying to connect to sftp: " f"{ret.before}") if not sftp_connected: ret.close() raise Exception("Unable to connect via SFTP to " f"{self.get_upload_url_string()}") put_cmd = (f'put {self.upload_archive_name} ' f'{self._get_sftp_upload_name()}') ret.sendline(put_cmd) put_expects = [ '100%', pexpect.TIMEOUT, pexpect.EOF, 'No such file or directory' ] put_success = ret.expect(put_expects, timeout=180) if put_success == 0: ret.sendline('bye') return True if put_success == 1: raise Exception("Timeout expired while uploading") if put_success == 2: raise Exception(f"Unknown error during upload: {ret.before}") if put_success == 3: raise Exception("Unable to write archive to destination") raise Exception(f"Unexpected response from server: {ret.before}") def _get_sftp_upload_name(self): """If a specific file name pattern is required by the SFTP server, override this method in the relevant Policy. Otherwise the archive's name on disk will be used :returns: Filename as it will exist on the SFTP server :rtype: ``str`` """ fname = self.upload_archive_name.split('/')[-1] if self.upload_directory: fname = os.path.join(self.upload_directory, fname) return fname def _upload_https_put(self, archive, verify=True): """If upload_https() needs to use requests.put(), use this method. Policies should override this method instead of the base upload_https() :param archive: The open archive file object """ return requests.put(self.get_upload_url(), data=archive, auth=self.get_upload_https_auth(), verify=verify, timeout=TIMEOUT_DEFAULT) def _get_upload_headers(self): """Define any needed headers to be passed with the POST request here """ return {} def _upload_https_post(self, archive, verify=True): """If upload_https() needs to use requests.post(), use this method. Policies should override this method instead of the base upload_https() :param archive: The open archive file object """ files = { 'file': (archive.name.split('/')[-1], archive, self._get_upload_headers()) } return requests.post(self.get_upload_url(), files=files, auth=self.get_upload_https_auth(), verify=verify, timeout=TIMEOUT_DEFAULT) def upload_https(self): """Attempts to upload the archive to an HTTPS location. :returns: ``True`` if upload is successful :rtype: ``bool`` :raises: ``Exception`` if upload was unsuccessful """ if not REQUESTS_LOADED: raise Exception("Unable to upload due to missing python requests " "library") with open(self.upload_archive_name, 'rb') as arc: if self.commons['cmdlineopts'].upload_method == 'auto': method = self._upload_method else: method = self.commons['cmdlineopts'].upload_method verify = self.commons['cmdlineopts'].upload_no_ssl_verify is False if method == 'put': r = self._upload_https_put(arc, verify) else: r = self._upload_https_post(arc, verify) if r.status_code not in (200, 201): if r.status_code == 401: raise Exception( "Authentication failed: invalid user credentials" ) raise Exception(f"POST request returned {r.status_code}: " f"{r.reason}") return True def upload_ftp(self, url=None, directory=None, user=None, password=None): """Attempts to upload the archive to either the policy defined or user provided FTP location. :param url: The URL to upload to :type url: ``str`` :param directory: The directory on the FTP server to write to :type directory: ``str`` or ``None`` :param user: The user to authenticate with :type user: ``str`` :param password: The password to use for `user` :type password: ``str`` :returns: ``True`` if upload is successful :rtype: ``bool`` :raises: ``Exception`` if upload in unsuccessful """ try: import ftplib import socket except ImportError: # socket is part of the standard library, should only fail here on # ftplib raise Exception("missing python ftplib library") if not url: url = self.get_upload_url() if url is None: raise Exception("no FTP server specified by policy, use --upload-" "url to specify a location") url = url.replace('ftp://', '') if not user: user = self.get_upload_user() if not password: password = self.get_upload_password() if not directory: directory = self.upload_directory or self._upload_directory try: session = ftplib.FTP(url, user, password, timeout=15) if not session: raise Exception("connection failed, did you set a user and " "password?") session.cwd(directory) except socket.timeout: raise Exception(f"timeout hit while connecting to {url}") except socket.gaierror: raise Exception(f"unable to connect to {url}") except ftplib.error_perm as err: errno = str(err).split()[0] if errno == '503': raise Exception(f"could not login as '{user}'") if errno == '530': raise Exception(f"invalid password for user '{user}'") if errno == '550': raise Exception("could not set upload directory to " f"{directory}") raise Exception(f"error trying to establish session: {str(err)}") try: with open(self.upload_archive_name, 'rb') as _arcfile: session.storbinary( f"STOR {self.upload_archive_name.split('/')[-1]}", _arcfile ) session.quit() return True except IOError: raise Exception("could not open archive file") def upload_s3(self, endpoint=None, region=None, bucket=None, prefix=None, access_key=None, secret_key=None): """Attempts to upload the archive to an S3 bucket. :param endpoint: The S3 endpoint to upload to :type endpoint: str :param region: The S3 region to upload to :type region: str :param bucket: The name of the S3 bucket to upload to :type bucket: str :param prefix: The prefix for the S3 object/key :type prefix: str :param access_key: The access key for the S3 bucket :type access_key: str :param secret_key: The secret key for the S3 bucket :type secret_key: str :returns: True if upload is successful :rtype: bool :raises: Exception if upload is unsuccessful """ if not BOTO3_LOADED: raise Exception("Unable to upload due to missing python boto3 " "library") if not endpoint: endpoint = self.get_upload_s3_endpoint() if not region: region = self.get_upload_s3_region() if not bucket: bucket = self.get_upload_s3_bucket().strip('/') if not prefix: prefix = self.get_upload_s3_object_prefix() if prefix != '' and prefix.startswith('/'): prefix = prefix[1:] if prefix != '' and not prefix.endswith('/'): prefix = f'{prefix}/' if prefix else '' if not access_key: access_key = self.get_upload_s3_access_key() if not secret_key: secret_key = self.get_upload_s3_secret_key() s3_client = boto3.client('s3', endpoint_url=endpoint, region_name=region, aws_access_key_id=access_key, aws_secret_access_key=secret_key) try: key = prefix + self.upload_archive_name.split('/')[-1] s3_client.upload_file(self.upload_archive_name, bucket, key) return True except Exception as e: raise Exception(f"Failed to upload to S3: {str(e)}") from e def set_sos_prefix(self): """If sos report commands need to always be prefixed with something, for example running in a specific container image, then it should be defined here. If no prefix should be set, return an empty string instead of None. """ return '' def set_cleanup_cmd(self): """If a host requires additional cleanup, the command should be set and returned here """ return '' # pylint: disable=unused-argument def create_sos_container(self, image=None, auth=None, force_pull=False): """Returns the command that will create the container that will be used for running commands inside a container on hosts that require it. This will use the container runtime defined for the host type to launch a container. From there, we use the defined runtime to exec into the container's namespace. :param image: The name of the image if not using the policy default :type image: ``str`` or ``None`` :param auth: The auth string required by the runtime to pull an image from the registry :type auth: ``str`` or ``None`` :param force_pull: Should the runtime forcibly pull the image :type force_pull: ``bool`` :returns: The command to execute to launch the temp container :rtype: ``str`` """ return '' def restart_sos_container(self): # pylint: disable=no-member """Restarts the container created for sos collect if it has stopped. This is called immediately after create_sos_container() as the command to create the container will exit and the container will stop. For current container runtimes, subsequently starting the container will default to opening a bash shell in the container to keep it running, thus allowing us to exec into it again. """ return f"{self.container_runtime} start {self.sos_container_name}" def format_container_command(self, cmd): # pylint: disable=no-member """Returns the command that allows us to exec into the created container for sos collect. :param cmd: The command to run in the sos container :type cmd: ``str`` :returns: The command to execute to run `cmd` in the container :rtype: ``str`` """ if self.container_runtime: return (f'{self.container_runtime} exec {self.sos_container_name} ' f'{cmd}') return cmd class GenericLinuxPolicy(LinuxPolicy): """This Policy will be returned if no other policy can be loaded. This should allow for IndependentPlugins to be executed on any system""" vendor_urls = [('Upstream Project', 'https://github.com/sosreport/sos')] vendor = 'SoS' vendor_text = ('SoS was unable to determine that the distribution of this ' 'system is supported, and has loaded a generic ' 'configuration. This may not provide desired behavior, and ' 'users are encouraged to request a new distribution-specifc' ' policy at the GitHub project above.\n') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/ubuntu.py0000664000175000017500000000676314660147624017001 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import UbuntuPlugin from sos.policies.distros.debian import DebianPolicy from sos.policies.package_managers.snap import SnapPackageManager from sos.policies.package_managers.dpkg import DpkgPackageManager from sos.policies.package_managers import MultiPackageManager class UbuntuPolicy(DebianPolicy): distro = "Ubuntu" vendor = "Canonical" vendor_urls = [ ('Community Website', 'https://www.ubuntu.com/'), ('Commercial Support', 'https://www.canonical.com') ] PATH = "/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" \ + ":/usr/local/sbin:/usr/local/bin:/snap/bin" _upload_url = "https://files.support.canonical.com/uploads/" _upload_user = "ubuntu" _upload_password = "ubuntu" _upload_method = 'put' def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.package_manager = MultiPackageManager( primary=DpkgPackageManager, fallbacks=[SnapPackageManager], chroot=self.sysroot, remote_exec=remote_exec) try: if self.package_manager.pkg_by_name( 'sosreport')['pkg_manager'] == 'snap': self.sos_bin_path = '/snap/bin' except TypeError: # Use the default sos_bin_path pass self.valid_subclasses += [UbuntuPlugin] @classmethod def check(cls, remote=''): """This method checks to see if we are running on Ubuntu. It returns True or False.""" if remote: return cls.distro in remote try: with open('/etc/lsb-release', 'r', encoding='utf-8') as fp: return "Ubuntu" in fp.read() except IOError: return False def dist_version(self): """ Returns the version stated in DISTRIB_RELEASE """ try: with open('/etc/lsb-release', 'r', encoding='utf-8') as fp: lines = fp.readlines() for line in lines: if "DISTRIB_RELEASE" in line: return float(line.split("=")[1].strip()) return False except (IOError, ValueError): return False def get_upload_https_auth(self, user=None, password=None): if self.upload_url.startswith(self._upload_url): return (self._upload_user, self._upload_password) return super().get_upload_https_auth() def get_upload_url_string(self): if self.upload_url.startswith(self._upload_url): return "Canonical Support File Server" return self.get_upload_url() def get_upload_url(self): if not self.upload_url or self.upload_url.startswith(self._upload_url): if not self.upload_archive_name: return self._upload_url fname = os.path.basename(self.upload_archive_name) return self._upload_url + fname return super().get_upload_url() # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/cloudlinux.py0000664000175000017500000000273414660147624017637 0ustar arifarif# Copyright (C) Eduard Abdullin # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class CloudLinuxPolicy(RedHatPolicy): distro = "CloudLinux" vendor = "CloudLinux" vendor_urls = [ ('Distribution Website', 'https://www.cloudlinux.com/'), ('Commercial Support', 'https://www.cloudlinux.com/') ] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.isfile('/etc/cloudlinux-release'): return False if os.path.exists(OS_RELEASE): with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'CloudLinux' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/debian.py0000664000175000017500000000510414660147624016665 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.report.plugins import DebianPlugin from sos.policies.distros import LinuxPolicy from sos.policies.package_managers.dpkg import DpkgPackageManager class DebianPolicy(LinuxPolicy): distro = "Debian" vendor = "the Debian project" vendor_urls = [('Community Website', 'https://www.debian.org/')] name_pattern = 'friendly' valid_subclasses = [DebianPlugin] PATH = "/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" \ + ":/usr/local/sbin:/usr/local/bin" sos_pkg_name = 'sosreport' deb_versions = { 'squeeze': 6, 'wheezy': 7, 'jessie': 8, 'stretch': 9, 'buster': 10, 'bullseye': 11, 'bookworm': 12, 'trixie': 13, 'forky': 14, } def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.package_manager = DpkgPackageManager(chroot=self.sysroot, remote_exec=remote_exec) self.valid_subclasses += [DebianPlugin] def _get_pkg_name_for_binary(self, binary): # for binary not specified inside {..}, return binary itself return { "xz": "xz-utils" }.get(binary, binary) @classmethod def check(cls, remote=''): """This method checks to see if we are running on Debian. It returns True or False.""" if remote: return cls.distro in remote return os.path.isfile('/etc/debian_version') def dist_version(self): try: with open('/etc/os-release', 'r', encoding='utf-8') as fp: rel_string = "" lines = fp.readlines() for line in lines: if "VERSION_CODENAME" in line: rel_string = line.split("=")[1].strip() break if rel_string in self.deb_versions: return self.deb_versions[rel_string] return False except IOError: return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/rocky.py0000664000175000017500000000324114660147624016572 0ustar arifarif# Copyright (C) Louis Abel # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class RockyPolicy(RedHatPolicy): distro = "Rocky Linux" vendor = "Rocky Enterprise Software Foundation" vendor_urls = [ ('Distribution Website', 'https://rockylinux.org'), ('Vendor Website', 'https://resf.org') ] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote # Return False if /etc/os-release is missing if not os.path.exists(OS_RELEASE): return False # Return False if /etc/rocky-release is missing if not os.path.isfile('/etc/rocky-release'): return False # If we've gotten this far, check for Rocky in # /etc/os-release with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'Rocky Linux' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/suse.py0000664000175000017500000000602014660147624016420 0ustar arifarif# Copyright (C) 2015 Red Hat, Inc. Bryn M. Reeves # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import sys from sos.report.plugins import RedHatPlugin, SuSEPlugin from sos.policies.distros import LinuxPolicy from sos.policies.package_managers.rpm import RpmPackageManager from sos import _sos as _ class SuSEPolicy(LinuxPolicy): distro = "SuSE" vendor = "SuSE" vendor_urls = [('Distribution Website', 'https://www.suse.com/')] _tmp_dir = "/var/tmp" def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.valid_subclasses += [SuSEPlugin, RedHatPlugin] self.usrmove = False self.package_manager = RpmPackageManager() # If rpm query timed out after timeout duration exit if not self.package_manager.packages: self.ui_log.error("Could not obtain installed package list.") sys.exit(1) self.PATH = "/usr/sbin:/usr/bin:/root/bin:/sbin" self.PATH += os.pathsep + "/usr/local/bin" self.PATH += os.pathsep + "/usr/local/sbin" self.set_exec_path() @classmethod def check(cls, remote=''): """This method checks to see if we are running on SuSE. It must be overriden by concrete subclasses to return True when running on an OpenSuSE, SLES or other Suse distribution and False otherwise.""" return False def get_tmp_dir(self, opt_tmp_dir): if not opt_tmp_dir: return self._tmp_dir return opt_tmp_dir def get_local_name(self): return self.host_name() class OpenSuSEPolicy(SuSEPolicy): distro = "OpenSuSE" vendor = "SuSE" vendor_urls = [('Community Website', 'https://www.opensuse.org/')] msg = _("""\ This command will collect diagnostic and configuration \ information from this %(distro)s system and installed \ applications. An archive containing the collected information will be \ generated in %(tmpdir)s and may be provided to a %(vendor)s \ support representative. No changes will be made to system configuration. %(vendor_text)s """) def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): """This method checks to see if we are running on SuSE. """ if remote: return cls.distro in remote return os.path.isfile('/etc/SUSE-brand') sos-4.8.0/sos/policies/distros/anolis.py0000664000175000017500000000271214660147624016732 0ustar arifarif# This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class AnolisPolicy(RedHatPolicy): distro = "Anolis OS" vendor = "The OpenAnolis Project" vendor_urls = [('Distribution Website', 'https://openanolis.org/')] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote # Return False if /etc/os-release is missing if not os.path.exists(OS_RELEASE): return False # Return False if /etc/anolis-release is missing if not os.path.isfile('/etc/anolis-release'): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'Anolis OS' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/opencloudos.py0000664000175000017500000000253214660147624017777 0ustar arifarif# Copyright (c) 2022 Tencent., ZoeDong # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class OpenCloudOSPolicy(RedHatPolicy): distro = "OpenCloudOS Stream" vendor = "OpenCloudOS" vendor_urls = [('Distribution Website', 'https://www.opencloudos.org/')] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.exists(OS_RELEASE): return False with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'OpenCloudOS Stream' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/almalinux.py0000664000175000017500000000276314660147624017445 0ustar arifarif# Copyright (C) Eduard Abdullin # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE class AlmaLinuxPolicy(RedHatPolicy): distro = "AlmaLinux" vendor = "AlmaLinux OS Foundation" vendor_urls = [ ('Distribution Website', 'https://www.almalinux.org/'), ('Commercial Support', 'https://tuxcare.com/linux-support-services/') ] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return cls.distro in remote if not os.path.isfile('/etc/almalinux-release'): return False if os.path.exists(OS_RELEASE): with open(OS_RELEASE, 'r', encoding='utf-8') as f: for line in f: if line.startswith('NAME'): if 'AlmaLinux' in line: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/redhat.py0000664000175000017500000006077514660147624016731 0ustar arifarif# Copyright (C) Steve Conklin # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import os import sys import re from sos.policies.auth import DeviceAuthorizationClass from sos.report.plugins import RedHatPlugin from sos.presets.redhat import (RHEL_PRESETS, RHV, RHEL, CB, RHOSP, RHOCP, RH_CFME, RH_SATELLITE, AAPEDA, AAPCONTROLLER) from sos.policies.distros import LinuxPolicy, ENV_HOST_SYSROOT from sos.policies.package_managers.rpm import RpmPackageManager from sos.policies.package_managers.flatpak import FlatpakPackageManager from sos.policies.package_managers import MultiPackageManager from sos.utilities import bold, convert_bytes, TIMEOUT_DEFAULT from sos import _sos as _ try: import requests REQUESTS_LOADED = True except ImportError: REQUESTS_LOADED = False OS_RELEASE = "/etc/os-release" RHEL_RELEASE_STR = "Red Hat Enterprise Linux" class RedHatPolicy(LinuxPolicy): distro = "Red Hat" vendor = "Red Hat" vendor_urls = [ ('Distribution Website', 'https://www.redhat.com/'), ('Commercial Support', 'https://access.redhat.com/') ] _tmp_dir = "/var/tmp" _in_container = False name_pattern = 'friendly' upload_url = None upload_user = None default_container_runtime = 'podman' sos_pkg_name = 'sos' sos_bin_path = '/usr/sbin' client_identifier_url = "https://sso.redhat.com/auth/"\ "realms/redhat-external/protocol/openid-connect/auth/device" token_endpoint = "https://sso.redhat.com/auth/realms/"\ "redhat-external/protocol/openid-connect/token" def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.usrmove = False self.package_manager = MultiPackageManager( primary=RpmPackageManager, fallbacks=[FlatpakPackageManager], chroot=self.sysroot, remote_exec=remote_exec) self.valid_subclasses += [RedHatPlugin] self.pkgs = self.package_manager.packages # If rpm query failed, exit if not self.pkgs: sys.stderr.write("Could not obtain installed package list") sys.exit(1) self.usrmove = self.check_usrmove(self.pkgs) if self.usrmove: self.PATH = "/usr/sbin:/usr/bin:/root/bin" else: self.PATH = "/sbin:/bin:/usr/sbin:/usr/bin:/root/bin" self.PATH += os.pathsep + "/usr/local/bin" self.PATH += os.pathsep + "/usr/local/sbin" if not self.remote_exec: self.set_exec_path() self.load_presets() @classmethod def check(cls, remote=''): """This method checks to see if we are running on Red Hat. It must be overriden by concrete subclasses to return True when running on a Fedora, RHEL or other Red Hat distribution or False otherwise. If `remote` is provided, it should be the contents of a remote host's os-release, or comparable, file to be used in place of the locally available one. """ return False @classmethod def display_distro_help(cls, section): if cls is not RedHatPolicy: super(RedHatPolicy, cls).display_distro_help(section) return section.add_text( 'This policy is a building block for all other Red Hat family ' 'distributions. You are likely looking for one of the ' 'distributions listed below.\n' ) subs = { 'centos': CentOsPolicy, 'rhel': RHELPolicy, 'redhatcoreos': RedHatCoreOSPolicy, 'fedora': FedoraPolicy } for subc, value in subs.items(): subln = bold(f"policies.{subc}") section.add_text( f"{' ':>8}{subln:<35}{value.distro:<30}", newline=False ) def check_usrmove(self, pkgs): """Test whether the running system implements UsrMove. If the 'filesystem' package is present, it will check that the version is greater than 3. If the package is not present the '/bin' and '/sbin' paths are checked and UsrMove is assumed if both are symbolic links. :param pkgs: a packages dictionary """ if 'filesystem' not in pkgs: return os.path.islink('/bin') and os.path.islink('/sbin') filesys_version = pkgs['filesystem']['version'] return filesys_version[0] == '3' def mangle_package_path(self, files): """Mangle paths for post-UsrMove systems. If the system implements UsrMove, all files will be in '/usr/[s]bin'. This method substitutes all the /[s]bin references in the 'files' list with '/usr/[s]bin'. :param files: the list of package managed files """ paths = [] def transform_path(path): # Some packages actually own paths in /bin: in this case, # duplicate the path as both the / and /usr version. skip_paths = ["/bin/rpm", "/bin/mailx"] if path in skip_paths: return (path, os.path.join("/usr", path[1:])) return (re.sub(r'(^)(/s?bin)', r'\1/usr\2', path),) if self.usrmove: for f in files: paths.extend(transform_path(f)) return paths return files def get_tmp_dir(self, opt_tmp_dir): if not opt_tmp_dir: return self._tmp_dir return opt_tmp_dir # Legal disclaimer text for Red Hat products disclaimer_text = """ Any information provided to %(vendor)s will be treated in \ accordance with the published support policies at:\n %(vendor_urls)s The generated archive may contain data considered sensitive \ and its content should be reviewed by the originating \ organization before being passed to any third party. No changes will be made to system configuration. """ RH_API_HOST = "https://api.access.redhat.com" RH_SFTP_HOST = "sftp://sftp.access.redhat.com" class RHELPolicy(RedHatPolicy): """ The RHEL policy is used specifically for Red Hat Enterprise Linux, of any release, and not forks or derivative distributions. For example, this policy will be loaded for any RHEL 8 installation, but will not be loaded for CentOS Stream 8 or Red Hat CoreOS, for which there are separate policies. Plugins activated by installed packages will only be activated if those packages are installed via RPM (dnf/yum inclusive). Packages installed by other means are not considered by this policy. By default, --upload will be directed to using the SFTP location provided by Red Hat for technical support cases. Users who provide login credentials for their Red Hat Customer Portal account will have their archives uploaded to a user-specific directory. If users provide those credentials as well as a case number, --upload will instead attempt to directly upload archives to the referenced case, thus streamlining the process of providing data to technical support engineers. If either or both of the credentials or case number are omitted or are incorrect, then a temporary anonymous user will be used for upload to the SFTP server, and users will need to provide that information to their technical support engineer. This information will be printed at the end of the upload process for any sos report execution. """ distro = RHEL_RELEASE_STR vendor = "Red Hat" msg = _("""\ This command will collect diagnostic and configuration \ information from this %(distro)s system and installed \ applications. An archive containing the collected information will be \ generated in %(tmpdir)s and may be provided to a %(vendor)s \ support representative. """ + disclaimer_text + "%(vendor_text)s\n") _upload_url = RH_SFTP_HOST _upload_method = 'post' _device_token = None # Max size for an http single request is 1Gb _max_size_request = 1073741824 def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.register_presets(RHEL_PRESETS) @classmethod def check(cls, remote=''): """Test to see if the running host is a RHEL installation. Checks for the presence of the "Red Hat Enterprise Linux" release string at the beginning of the NAME field in the `/etc/os-release` file and returns ``True`` if it is found, and ``False`` otherwise. :returns: ``True`` if the host is running RHEL or ``False`` otherwise. """ if remote: return cls.distro in remote if not os.path.exists(OS_RELEASE): return False with open(OS_RELEASE, "r", encoding='utf-8') as f: for line in f: if line.startswith("NAME"): (_, value) = line.split("=") value = value.strip("\"'") if value.startswith(cls.distro): return True return False def prompt_for_upload_user(self): if self.commons['cmdlineopts'].upload_user: self.ui_log.info( _("The option --upload-user has been deprecated in favour" " of device authorization in RHEL") ) if not self.case_id: # no case id provided => failover to SFTP self.upload_url = RH_SFTP_HOST self.ui_log.info("No case id provided, uploading to SFTP") def prompt_for_upload_password(self): # With OIDC we don't ask for user/pass anymore if self.commons['cmdlineopts'].upload_pass: self.ui_log.info( _("The option --upload-pass has been deprecated in favour" " of device authorization in RHEL") ) def get_upload_url(self): if self.upload_url: return self.upload_url if self.commons['cmdlineopts'].upload_url: return self.commons['cmdlineopts'].upload_url if self.commons['cmdlineopts'].upload_protocol == 'sftp': return RH_SFTP_HOST if not self.commons['cmdlineopts'].case_id: self.ui_log.info("No case id provided, uploading to SFTP") return RH_SFTP_HOST rh_case_api = "/support/v1/cases/%s/attachments" return RH_API_HOST + rh_case_api % self.case_id def _get_upload_https_auth(self): str_auth = f"Bearer {self._device_token}" return {'Authorization': str_auth} def _upload_https_post(self, archive, verify=True): """If upload_https() needs to use requests.post(), use this method. Policies should override this method instead of the base upload_https() :param archive: The open archive file object """ files = { 'file': (archive.name.split('/')[-1], archive, self._get_upload_headers()) } # Get the access token at this point. With this, # we cover the cases where report generation takes # longer than the token timeout RHELAuth = DeviceAuthorizationClass( self.client_identifier_url, self.token_endpoint ) self._device_token = RHELAuth.get_access_token() self.ui_log.info("Device authorized correctly. Uploading file to " f"{self.get_upload_url_string()}") return requests.post(self.get_upload_url(), files=files, headers=self._get_upload_https_auth(), verify=verify, timeout=TIMEOUT_DEFAULT) def _get_upload_headers(self): if self.get_upload_url().startswith(RH_API_HOST): return {'isPrivate': 'false', 'cache-control': 'no-cache'} return {} def get_upload_url_string(self): if self.get_upload_url().startswith(RH_API_HOST): return "Red Hat Customer Portal" if self.get_upload_url().startswith(RH_SFTP_HOST): return "Red Hat Secure FTP" return self.upload_url def _get_sftp_upload_name(self): """The RH SFTP server will only automatically connect file uploads to cases if the filename _starts_ with the case number """ fname = self.upload_archive_name.split('/')[-1] if self.case_id: fname = f"{self.case_id}_{fname}" if self.upload_directory: fname = os.path.join(self.upload_directory, fname) return fname # pylint: disable=too-many-branches def upload_sftp(self, user=None, password=None): """Override the base upload_sftp to allow for setting an on-demand generated anonymous login for the RH SFTP server if a username and password are not given """ if RH_SFTP_HOST.split('//')[1] not in self.get_upload_url(): return super().upload_sftp() if not REQUESTS_LOADED: raise Exception("python3-requests is not installed and is required" " for obtaining SFTP auth token.") _token = None _user = None # We may have a device token already if we attempted # to upload via http but the upload failed. So # lets check first if there isn't one. if not self._device_token: try: RHELAuth = DeviceAuthorizationClass( self.client_identifier_url, self.token_endpoint ) except Exception as e: # We end up here if the user cancels the device # authentication in the web interface if "end user denied" in str(e): self.ui_log.info( "Device token authorization " "has been cancelled by the user." ) else: self._device_token = RHELAuth.get_access_token() if self._device_token: self.ui_log.info("Device authorized correctly. Uploading file to" f" {self.get_upload_url_string()}") url = RH_API_HOST + '/support/v2/sftp/token' ret = None if self._device_token: headers = self._get_upload_https_auth() ret = requests.post(url, headers=headers, timeout=10) if ret.status_code == 200: # credentials are valid _user = json.loads(ret.text)['username'] _token = json.loads(ret.text)['token'] else: self.ui_log.debug( f"DEBUG: auth attempt failed (status: {ret.status_code}): " f"{ret.json()}" ) self.ui_log.error( "Unable to retrieve Red Hat auth token using provided " "credentials. Will try anonymous." ) else: adata = {"isAnonymous": True} anon = requests.post(url, data=json.dumps(adata), timeout=10) if anon.status_code == 200: resp = json.loads(anon.text) _user = resp['username'] _token = resp['token'] self.ui_log.info( _(f"User {_user} used for anonymous upload. Please inform " f"your support engineer so they may retrieve the data.") ) else: self.ui_log.debug( f"DEBUG: anonymous request failed (status: " f"{anon.status_code}): {anon.json()}" ) if _user and _token: return super().upload_sftp(user=_user, password=_token) raise Exception("Could not retrieve valid or anonymous credentials") def check_file_too_big(self, archive): size = os.path.getsize(archive) # Lets check if the size is bigger than the limit. # There's really no need to transform the size to Gb, # so we don't need to call any size converter implemented # in tools.py if size >= self._max_size_request: self.ui_log.warning( _("Size of archive is bigger than Red Hat Customer Portal " "limit for uploads of " f"{convert_bytes(self._max_size_request)} " " via sos http upload. \n") ) return RH_SFTP_HOST return RH_API_HOST def upload_archive(self, archive): """Override the base upload_archive to provide for automatic failover from RHCP failures to the public RH dropbox """ try: if self.get_upload_url().startswith(RH_API_HOST): self.upload_url = self.check_file_too_big(archive) uploaded = super().upload_archive(archive) except Exception as e: uploaded = False if not self.upload_url.startswith(RH_API_HOST): raise self.ui_log.error( _(f"Upload to Red Hat Customer Portal failed due to " f"{e}. Trying {RH_SFTP_HOST}") ) self.upload_url = RH_SFTP_HOST uploaded = super().upload_archive(archive) return uploaded def dist_version(self): try: rr = self.package_manager.all_pkgs_by_name_regex("redhat-release*") pkgname = self.pkgs[rr[0]]["version"] # this should always map to the major version number. This will not # be so on RHEL 5, but RHEL 5 does not support python3 and thus # should never run a version of sos with this check return int(pkgname[0]) except Exception: pass return False def probe_preset(self): # Emergency or rescue mode? for target in ["rescue", "emergency"]: if self.init_system.is_running(f"{target}.target", False): return self.find_preset(CB) # Package based checks if self.pkg_by_name("satellite-common") is not None: return self.find_preset(RH_SATELLITE) if self.pkg_by_name("rhosp-release") is not None: return self.find_preset(RHOSP) if self.pkg_by_name("cfme") is not None: return self.find_preset(RH_CFME) if self.pkg_by_name("ovirt-engine") is not None or \ self.pkg_by_name("vdsm") is not None: return self.find_preset(RHV) if self.pkg_by_name("automation-controller-server") is not None: return self.find_preset(AAPCONTROLLER) for pkg in ['automation-eda-controller', 'automation-eda-controller-server']: if self.pkg_by_name(pkg) is not None: return self.find_preset(AAPEDA) # Vanilla RHEL is default return self.find_preset(RHEL) class CentOsPolicy(RHELPolicy): distro = "CentOS" vendor = "CentOS" vendor_urls = [('Community Website', 'https://www.centos.org/')] class RedHatCoreOSPolicy(RHELPolicy): """ Red Hat CoreOS is a containerized host built upon Red Hat Enterprise Linux and as such this policy is built on top of the RHEL policy. For users, this should be entirely transparent as any behavior exhibited or influenced on RHEL systems by that policy will be seen on RHCOS systems as well. The one change is that this policy ensures that sos collect will deploy a container on RHCOS systems in order to facilitate sos report collection, as RHCOS discourages non-default package installation via rpm-ostree which is used to maintain atomicity for RHCOS nodes. The default container image used by this policy is the support-tools image maintained by Red Hat on registry.redhat.io. Note that this policy is only loaded when sos is directly run on an RHCOS node - if sos collect uses the `oc` transport (the default transport that will be attempted by the ocp cluster profile), then the policy loaded inside the launched pod will be RHEL. Again, this is expected and will not impact how sos report collections are performed. """ distro = "Red Hat CoreOS" msg = _("""\ This command will collect diagnostic and configuration \ information from this %(distro)s system. An archive containing the collected information will be \ generated in %(tmpdir)s and may be provided to a %(vendor)s \ support representative. """ + disclaimer_text + "%(vendor_text)s\n") containerized = True container_runtime = 'podman' container_image = 'registry.redhat.io/rhel8/support-tools' sos_path_strip = '/host' container_version_command = 'rpm -q sos' container_authfile = '/var/lib/kubelet/config.json' def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): if remote: return 'CoreOS' in remote coreos = False if ENV_HOST_SYSROOT not in os.environ: return coreos host_release = os.environ[ENV_HOST_SYSROOT] + OS_RELEASE try: with open(host_release, 'r', encoding='utf-8') as hfile: for line in hfile.read().splitlines(): coreos |= 'Red Hat Enterprise Linux CoreOS' in line except IOError: # host release file not present, will fallback to RHEL policy check pass return coreos def probe_preset(self): # As of the creation of this policy, RHCOS is only available for # RH OCP environments. return self.find_preset(RHOCP) def create_sos_container(self, image=None, auth=None, force_pull=False): _image = image or self.container_image _pull = '--pull=always' if force_pull else '' return ( f"{self.container_runtime} run -di " f"--name {self.sos_container_name} --privileged --ipc=host " f"--net=host --pid=host -e HOST=/host " f"-e NAME={self.sos_container_name} -e " f"IMAGE={_image} {_pull} " f"-v /run:/run -v /var/log:/var/log " f"-v /etc/machine-id:/etc/machine-id " f"-v /etc/localtime:/etc/localtime " f"-v /:/host " f"{auth or ''} {_image}" ) def set_cleanup_cmd(self): return f'podman rm --force {self.sos_container_name}' class FedoraPolicy(RedHatPolicy): """ The policy for Fedora based systems, regardless of spin/edition. This policy is based on the parent Red Hat policy, and thus will only check for RPM packages when considering packaged-based plugin enablement. Packages installed by other sources are not considered. There is no default --upload location for this policy. If users need to upload an sos report archive from a Fedora system, they will need to provide the location via --upload-url, and optionally login credentials for that location via --upload-user and --upload-pass (or the appropriate environment variables). """ distro = "Fedora" vendor = "the Fedora Project" vendor_urls = [ ('Community Website', 'https://fedoraproject.org/'), ('Community Forums', 'https://discussion.fedoraproject.org/') ] def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) @classmethod def check(cls, remote=''): """This method checks to see if we are running on Fedora. It returns True or False.""" if remote: return cls.distro in remote return os.path.isfile('/etc/fedora-release') def fedora_version(self): pkg = self.pkg_by_name("fedora-release") or \ self.package_manager.all_pkgs_by_name_regex( "fedora-release-.*")[-1] return int(pkg["version"]) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/distros/cos.py0000664000175000017500000000400114660147624016222 0ustar arifarif# Copyright (C) Red Hat, Inc. 2020 # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.report.plugins import CosPlugin, IndependentPlugin from sos.policies.distros import LinuxPolicy def _blank_or_comment(line): """Test whether line is empty of contains a comment. Test whether the ``line`` argument is either blank, or a whole-line comment. :param line: the line of text to be checked. :returns: ``True`` if the line is blank or a comment, and ``False`` otherwise. :rtype: bool """ return not line.strip() or line.lstrip().startswith('#') class CosPolicy(LinuxPolicy): distro = "Container-Optimized OS" vendor = "Google Cloud Platform" vendor_urls = [ ('Distribution Website', 'https://cloud.google.com/container-optimized-os/') ] valid_subclasses = [CosPlugin, IndependentPlugin] PATH = "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin" def __init__(self, sysroot=None, init=None, probe_runtime=True, remote_exec=None): super().__init__(sysroot=sysroot, init=init, probe_runtime=probe_runtime, remote_exec=remote_exec) self.valid_subclasses += [CosPolicy] @classmethod def check(cls, remote=''): if remote: return cls.distro in remote try: with open('/etc/os-release', 'r', encoding='utf-8') as fp: os_release = dict(line.strip().split('=') for line in fp if not _blank_or_comment(line)) return os_release['ID'] == 'cos' except (IOError, KeyError): return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/auth/0000775000175000017500000000000014660147624014343 5ustar arifarifsos-4.8.0/sos/policies/auth/__init__.py0000664000175000017500000002037614660147624016464 0ustar arifarif# Copyright (C) 2023 Red Hat, Inc., Jose Castillo # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import logging try: import requests REQUESTS_LOADED = True except ImportError: REQUESTS_LOADED = False import time from datetime import datetime, timedelta from sos.utilities import TIMEOUT_DEFAULT DEVICE_AUTH_CLIENT_ID = "sos-tools" GRANT_TYPE_DEVICE_CODE = "urn:ietf:params:oauth:grant-type:device_code" logger = logging.getLogger("sos") class DeviceAuthorizationClass: """ Device Authorization Class """ def __init__(self, client_identifier_url, token_endpoint): self._access_token = None self._access_expires_at = None self.__device_code = None self.client_identifier_url = client_identifier_url self.token_endpoint = token_endpoint self._use_device_code_grant() def _use_device_code_grant(self): """ Start the device auth flow. In the future we will store the tokens in an in-memory keyring. """ self._request_device_code() print( "Please visit the following URL to authenticate this" f" device: {self._verification_uri_complete}" ) self.poll_for_auth_completion() def _request_device_code(self): """ Initialize new Device Authorization Grant attempt by requesting a new device code. """ data = f"client_id={DEVICE_AUTH_CLIENT_ID}" headers = {'content-type': 'application/x-www-form-urlencoded'} if not REQUESTS_LOADED: raise Exception("python3-requests is not installed and is required" " for obtaining device auth token.") try: res = requests.post( self.client_identifier_url, data=data, headers=headers, timeout=TIMEOUT_DEFAULT) res.raise_for_status() response = res.json() self._user_code = response.get("user_code") self._verification_uri = response.get("verification_uri") self._interval = response.get("interval") self.__device_code = response.get("device_code") self._verification_uri_complete = response.get( "verification_uri_complete") except requests.HTTPError as e: raise requests.HTTPError("HTTP request failed " "while attempting to acquire the tokens." f"Error returned was {res.status_code} " f"{e}") def poll_for_auth_completion(self): """ Continuously poll OIDC token endpoint until the user is successfully authenticated or an error occurs. """ token_data = {'grant_type': GRANT_TYPE_DEVICE_CODE, 'client_id': DEVICE_AUTH_CLIENT_ID, 'device_code': self.__device_code} if not REQUESTS_LOADED: raise Exception("python3-requests is not installed and is required" " for obtaining device auth token.") while self._access_token is None: time.sleep(self._interval) try: check_auth_completion = requests.post(self.token_endpoint, data=token_data, timeout=TIMEOUT_DEFAULT) status_code = check_auth_completion.status_code if status_code == 200: logger.info("The SSO authentication is successful") self._set_token_data(check_auth_completion.json()) if status_code not in [200, 400]: raise Exception(status_code, check_auth_completion.text) if status_code == 400 and \ check_auth_completion.json()['error'] not in \ ("authorization_pending", "slow_down"): raise Exception(status_code, check_auth_completion.text) except requests.exceptions.RequestException as e: logger.error(f"Error was found while posting a request: {e}") def _set_token_data(self, token_data): """ Set the class attributes as per the input token_data received. In the future we will persist the token data in a local, in-memory keyring, to avoid visting the browser frequently. :param token_data: Token data containing access_token, refresh_token and their expiry etc. """ self._access_token = token_data.get("access_token") self._access_expires_at = datetime.utcnow() + \ timedelta(seconds=token_data.get("expires_in")) self._refresh_token = token_data.get("refresh_token") self._refresh_expires_in = token_data.get("refresh_expires_in") if self._refresh_expires_in == 0: self._refresh_expires_at = datetime.max else: self._refresh_expires_at = datetime.utcnow() + \ timedelta(seconds=self._refresh_expires_in) def get_access_token(self): """ Get the valid access_token at any given time. :return: Access_token :rtype: string """ if self.is_access_token_valid(): return self._access_token if self.is_refresh_token_valid(): self._use_refresh_token_grant() return self._access_token self._use_device_code_grant() return self._access_token def is_access_token_valid(self): """ Check the validity of access_token. We are considering it invalid 180 sec. prior to it's exact expiry time. :return: True/False """ return self._access_token and self._access_expires_at and \ self._access_expires_at - timedelta(seconds=180) > \ datetime.utcnow() def is_refresh_token_valid(self): """ Check the validity of refresh_token. We are considering it invalid 180 sec. prior to it's exact expiry time. :return: True/False """ return self._refresh_token and self._refresh_expires_at and \ self._refresh_expires_at - timedelta(seconds=180) > \ datetime.utcnow() def _use_refresh_token_grant(self, refresh_token=None): """ Fetch the new access_token and refresh_token using the existing refresh_token and persist it. :param refresh_token: optional param for refresh_token """ if not REQUESTS_LOADED: raise Exception("python3-requests is not installed and is required" " for obtaining device auth token.") refresh_token_data = {'client_id': DEVICE_AUTH_CLIENT_ID, 'grant_type': 'refresh_token', 'refresh_token': self._refresh_token if not refresh_token else refresh_token} refresh_token_res = requests.post(self.token_endpoint, data=refresh_token_data, timeout=TIMEOUT_DEFAULT) if refresh_token_res.status_code == 200: self._set_token_data(refresh_token_res.json()) elif refresh_token_res.status_code == 400 and 'invalid' in\ refresh_token_res.json()['error']: logger.warning("Problem while fetching the new tokens from refresh" f" token grant - {refresh_token_res.status_code} " f"{refresh_token_res.json()['error']}." " New Device code will be requested !") self._use_device_code_grant() else: raise Exception( "Something went wrong while using the " "Refresh token grant for fetching tokens:" f" Returned status code {refresh_token_res.status_code}" f" and error {refresh_token_res.json()['error']}") sos-4.8.0/sos/policies/runtimes/0000775000175000017500000000000014660147624015250 5ustar arifarifsos-4.8.0/sos/policies/runtimes/lxd.py0000664000175000017500000001251114660147624016411 0ustar arifarif# Copyright (C) 2023 Canonical Ltd., Arif Ali # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from sos.policies.runtimes import ContainerRuntime from sos.utilities import sos_get_command_output from sos.utilities import is_executable class LxdContainerRuntime(ContainerRuntime): """Runtime class to use for systems running LXD""" name = 'lxd' binary = 'lxc' def check_is_active(self): # the daemon must be running if (is_executable('lxc', self.policy.sysroot) and self.policy.package_manager.pkg_by_name('lxd') and (self.policy.init_system.is_running('lxd') or self.policy.init_system.is_running('snap.lxd.daemon'))): self.active = True return True return False def get_containers(self, get_all=False): """Get a list of containers present on the system. :param get_all: If set, include stopped containers as well :type get_all: ``bool`` """ containers = [] _cmd = f"{self.binary} list --format json" if self.active: out = sos_get_command_output(_cmd, chroot=self.policy.sysroot) if out["status"] == 0: out_json = json.loads(out["output"]) for container in out_json: if container['status'] == 'Running' or get_all: # takes the form (container_id, container_name) containers.append( (container['expanded_config']['volatile.uuid'], container['name'])) return containers def get_images(self): """Get a list of images present on the system :returns: A list of 2-tuples containing (image_name, image_id) :rtype: ``list`` """ images = [] if self.active: out = sos_get_command_output( f"{self.binary} image list --format json", chroot=self.policy.sysroot ) if out['status'] == 0: out_json = json.loads(out["output"]) for ent in out_json: # takes the form (image_name, image_id) if 'update_source' in ent: images.append(( ent['update_source']['alias'], ent['fingerprint'])) return images def get_volumes(self): """Get a list of container volumes present on the system :returns: A list of volume IDs on the system :rtype: ``list`` """ vols = [] stg_pool = "default" if self.active: # first get the default storage pool out = sos_get_command_output( f"{self.binary} profile list --format json", chroot=self.policy.sysroot ) if out['status'] == 0: out_json = json.loads(out['output']) for profile in out_json: if (profile['name'] == 'default' and 'root' in profile['devices']): stg_pool = profile['devices']['root']['pool'] break out = sos_get_command_output( f"{self.binary} storage volume list {stg_pool} --format json", chroot=self.policy.sysroot ) if out['status'] == 0: out_json = json.loads(out['output']) for ent in out_json: vols.append(ent['name']) return vols def get_logs_command(self, container): """Get the command string used to dump container logs from the runtime :param container: The name or ID of the container to get logs for :type container: ``str`` :returns: Formatted runtime command to get logs from `container` :type: ``str`` """ return f"{self.binary} info {container} --show-log" def get_copy_command(self, container, path, dest, sizelimit=None): """Generate the command string used to copy a file out of a container by way of the runtime. :param container: The name or ID of the container :type container: ``str`` :param path: The path to copy from the container. Note that at this time, no supported runtime supports globbing :type path: ``str`` :param dest: The destination on the *host* filesystem to write the file to :type dest: ``str`` :param sizelimit: Limit the collection to the last X bytes of the file at PATH :type sizelimit: ``int`` :returns: Formatted runtime command to copy a file from a container :rtype: ``str`` """ if sizelimit: return f"{self.run_cmd} {container} tail -c {sizelimit} {path}" return f"{self.binary} file pull {container}{path} {dest}" # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/runtimes/docker.py0000664000175000017500000000210714660147624017071 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.runtimes import ContainerRuntime from sos.utilities import is_executable class DockerContainerRuntime(ContainerRuntime): """Runtime class to use for systems running Docker""" name = 'docker' binary = 'docker' def check_is_active(self): # the daemon must be running if (is_executable('docker', self.policy.sysroot) and (self.policy.init_system.is_running('docker') or self.policy.init_system.is_running('snap.docker.dockerd'))): self.active = True return True return False def check_can_copy(self): return self.active # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/runtimes/__init__.py0000664000175000017500000002100514660147624017357 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from shlex import quote from sos.utilities import sos_get_command_output, is_executable class ContainerRuntime(): """Encapsulates a container runtime that provides the ability to plugins to check runtime status, check for the presence of specific containers, and to format commands to run in those containers :param policy: The loaded policy for the system :type policy: ``Policy()`` :cvar name: The name of the container runtime, e.g. 'podman' :vartype name: ``str`` :cvar containers: A list of containers known to the runtime :vartype containers: ``list`` :cvar images: A list of images known to the runtime :vartype images: ``list`` :cvar binary: The binary command to run for the runtime, must exit within $PATH :vartype binary: ``str`` """ name = 'Undefined' containers = [] images = [] volumes = [] binary = '' active = False def __init__(self, policy=None): self.policy = policy self.run_cmd = f"{self.binary} exec " def load_container_info(self): """If this runtime is found to be active, attempt to load information on the objects existing in the runtime. """ self.containers = self.get_containers() self.images = self.get_images() self.volumes = self.get_volumes() def check_is_active(self): """Check to see if the container runtime is both present AND active. Active in this sense means that the runtime can be used to glean information about the runtime itself and containers that are running. :returns: ``True`` if the runtime is active, else ``False`` :rtype: ``bool`` """ if is_executable(self.binary, self.policy.sysroot): self.active = True return True return False def check_can_copy(self): """Check if the runtime supports copying files out of containers and onto the host filesystem """ return True def get_containers(self, get_all=False): """Get a list of containers present on the system. :param get_all: If set, include stopped containers as well :type get_all: ``bool`` """ containers = [] _cmd = f"{self.binary} ps {'-a' if get_all else ''}" if self.active: out = sos_get_command_output(_cmd, chroot=self.policy.sysroot) if out['status'] == 0: for ent in out['output'].splitlines()[1:]: ent = ent.split() # takes the form (container_id, container_name) containers.append((ent[0], ent[-1])) return containers def get_container_by_name(self, name): """Get the container ID for the container matching the provided name :param name: The name of the container, note this can be a regex :type name: ``str`` :returns: The id of the first container to match `name`, else ``None`` :rtype: ``str`` """ if not self.active or name is None: return None for c in self.containers: if re.match(name, c[1]): return c[0] return None def get_images(self): """Get a list of images present on the system :returns: A list of 2-tuples containing (image_name, image_id) :rtype: ``list`` """ images = [] fmt = '{{lower .Repository}}:{{lower .Tag}} {{lower .ID}}' if self.active: out = sos_get_command_output( f"{self.binary} images --format '{fmt}'", chroot=self.policy.sysroot ) if out['status'] == 0: for ent in out['output'].splitlines(): ent = ent.split() # takes the form (image_name, image_id) images.append((ent[0], ent[1])) return images def get_volumes(self): """Get a list of container volumes present on the system :returns: A list of volume IDs on the system :rtype: ``list`` """ vols = [] if self.active: out = sos_get_command_output( f"{self.binary} volume ls", chroot=self.policy.sysroot ) if out['status'] == 0: for ent in out['output'].splitlines()[1:]: ent = ent.split() vols.append(ent[-1]) return vols def container_exists(self, container): """Check if a given container ID or name exists on the system from the perspective of the container runtime. Note that this will only check _running_ containers :param container: The name or ID of the container :type container: ``str`` :returns: True if the container exists, else False :rtype: ``bool`` """ for _contup in self.containers: if container in _contup: return True return False def fmt_container_cmd(self, container, cmd, quotecmd): """Format a command to run inside a container using the runtime :param container: The name or ID of the container in which to run :type container: ``str`` :param cmd: The command to run inside `container` :type cmd: ``str`` :param quotecmd: Whether the cmd should be quoted. :type quotecmd: ``bool`` :returns: Formatted string to run `cmd` inside `container` :rtype: ``str`` """ if quotecmd: quoted_cmd = quote(cmd) else: quoted_cmd = cmd return f"{self.run_cmd} {container} {quoted_cmd}" def fmt_registry_credentials(self, username, password): """Format a string to pass to the 'run' command of the runtime to enable authorization for pulling the image during `sos collect`, if needed using username and optional password creds :param username: The name of the registry user :type username: ``str`` :param password: The password of the registry user :type password: ``str`` or ``None`` :returns: The string to use to enable a run command to pull the image :rtype: ``str`` """ return f"--creds={username}{':' + password if password else ''}" def fmt_registry_authfile(self, authfile): """Format a string to pass to the 'run' command of the runtime to enable authorization for pulling the image during `sos collect`, if needed using an authfile. """ if authfile: return f"--authfile {authfile}" return '' def get_logs_command(self, container): """Get the command string used to dump container logs from the runtime :param container: The name or ID of the container to get logs for :type container: ``str`` :returns: Formatted runtime command to get logs from `container` :type: ``str`` """ return f"{self.binary} logs -t {container}" def get_copy_command(self, container, path, dest, sizelimit=None): """Generate the command string used to copy a file out of a container by way of the runtime. :param container: The name or ID of the container :type container: ``str`` :param path: The path to copy from the container. Note that at this time, no supported runtime supports globbing :type path: ``str`` :param dest: The destination on the *host* filesystem to write the file to :type dest: ``str`` :param sizelimit: Limit the collection to the last X bytes of the file at PATH :type sizelimit: ``int`` :returns: Formatted runtime command to copy a file from a container :rtype: ``str`` """ if sizelimit: return f"{self.run_cmd} {container} tail -c {sizelimit} {path}" return f"{self.binary} cp {container}:{path} {dest}" # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/runtimes/podman.py0000664000175000017500000000121114660147624017073 0ustar arifarif# Copyright (C) 2020 Red Hat, Inc., Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.policies.runtimes import ContainerRuntime class PodmanContainerRuntime(ContainerRuntime): """Runtime class to use for systems running Podman""" name = 'podman' binary = 'podman' # vim: set et ts=4 sw=4 : sos-4.8.0/sos/policies/runtimes/crio.py0000664000175000017500000000655514660147624016571 0ustar arifarif# Copyright (C) 2021 Red Hat, Inc., Nadia Pinaeva # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json from shlex import quote from sos.policies.runtimes import ContainerRuntime from sos.utilities import sos_get_command_output class CrioContainerRuntime(ContainerRuntime): """Runtime class to use for systems running crio""" name = 'crio' binary = 'crictl' def check_can_copy(self): return False def get_containers(self, get_all=False): """Get a list of containers present on the system. :param get_all: If set, include stopped containers as well :type get_all: ``bool`` """ containers = [] _cmd = f"{self.binary} ps {'-a' if get_all else ''} -o json" if self.active: out = sos_get_command_output(_cmd, chroot=self.policy.sysroot) if out["status"] == 0: out_json = json.loads(out["output"]) for container in out_json["containers"]: # takes the form (container_id, container_name) containers.append( (container["id"], container["metadata"]["name"])) return containers def get_images(self): """Get a list of images present on the system :returns: A list of 2-tuples containing (image_name, image_id) :rtype: ``list`` """ images = [] if self.active: out = sos_get_command_output(f"{self.binary} images -o json", chroot=self.policy.sysroot) if out['status'] == 0: out_json = json.loads(out["output"]) for image in out_json["images"]: # takes the form (repository:tag, image_id) if len(image["repoTags"]) > 0: for repo_tag in image["repoTags"]: images.append((repo_tag, image["id"])) else: if len(image["repoDigests"]) == 0: image_name = "" else: image_name = image["repoDigests"][0].split("@")[0] images.append((image_name + ":", image["id"])) return images def fmt_container_cmd(self, container, cmd, quotecmd): """Format a command to run inside a container using the runtime :param container: The name or ID of the container in which to run :type container: ``str`` :param cmd: The command to run inside `container` :type cmd: ``str`` :param quotecmd: Whether the cmd should be quoted. :type quotecmd: ``bool`` :returns: Formatted string to run `cmd` inside `container` :rtype: ``str`` """ if quotecmd: quoted_cmd = quote(cmd) else: quoted_cmd = cmd container_id = self.get_container_by_name(container) return (f"{self.run_cmd} {container_id} {quoted_cmd}" if container_id is not None else '') # vim: set et ts=4 sw=4 : sos-4.8.0/sos/component.py0000664000175000017500000004753014660147624014160 0ustar arifarif# Copyright 2020 Red Hat, Inc. # Author: Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import json import logging import os import tempfile import sys import time from argparse import SUPPRESS from datetime import datetime from getpass import getpass from shutil import rmtree from pathlib import Path from sos import __version__ from sos.archive import TarFileArchive from sos.options import SoSOptions from sos.utilities import TempFileUtil, shell_out class SoSComponent(): """Any sub-command that sos supports needs to subclass SoSComponent in order to be properly supported by the sos binary. This class contains the standardized entrypoint for subcommands, as well as building out supported options from both globally shared option lists, and options supported by that specific subcommand. When sos initializes, it will load an unintialized instance of each class found within one recursion of the module root directory that subclasses SoSComponent. If sos is able to match the user-specified subcommand to one that exists locally, then that SoSComponent is initialized, logging is setup, and a policy is loaded. From there, the component's execute() method takes over. Added in 4.0 """ desc = 'unset' arg_defaults = {} configure_logging = True load_policy = True load_probe = True root_required = False _arg_defaults = { "batch": False, "compression_type": 'auto', "config_file": '/etc/sos/sos.conf', "debug": False, "encrypt": False, "encrypt_key": None, "encrypt_pass": None, "quiet": False, "threads": 4, "tmp_dir": '', "sysroot": None, "verbosity": 0 } # files in collected archive that might contain upload password files_with_upload_passwd = [ "sos_logs/sos.log", "sos_reports/manifest.json", "sos_commands/process/ps_*", "sos_commands/selinux/ps_*", "sos_commands/systemd/systemctl_status_--all", ] def __init__(self, parser, parsed_args, cmdline_args): self.parser = parser self.args = parsed_args self.cmdline = cmdline_args self.exit_process = False self.archive = None self.tmpdir = None self.tempfile_util = None self.manifest = None try: import signal signal.signal(signal.SIGTERM, self.get_exit_handler()) except Exception as err: sys.stdout.write(f"Notice: Could not set SIGTERM handler: {err}\n") self.opts = SoSOptions(arg_defaults=self._arg_defaults) if self.load_policy: self.load_local_policy() # update args from component's arg_defaults definition self._arg_defaults.update(self.arg_defaults) self.opts = self.load_options() # lgtm [py/init-calls-subclass] if self.configure_logging: tmpdir = self.get_tmpdir_default() # only setup metadata if we are logging self.manifest = SoSMetadata() if not os.path.isdir(tmpdir) \ or not os.access(tmpdir, os.W_OK): msg = f"temporary directory {tmpdir} " msg += "does not exist or is not writable\n" # write directly to stderr as logging is not initialised yet sys.stderr.write(msg) self._exit(1) self.sys_tmp = tmpdir self.tmpdir = tempfile.mkdtemp(prefix="sos.", dir=self.sys_tmp) self.tempfile_util = TempFileUtil(self.tmpdir) self._setup_logging() if self.manifest is not None: self.manifest.add_field('version', __version__) self.manifest.add_field('cmdline', ' '.join(self.cmdline)) self.manifest.add_field('start_time', datetime.now()) # these three will be set later, add here for organization self.manifest.add_field('end_time', '') self.manifest.add_field('run_time', '') self.manifest.add_field('compression', '') self.manifest.add_field('tmpdir', self.tmpdir) self.manifest.add_field('tmpdir_fs_type', self.tmpfstype) self.manifest.add_field('policy', self.policy.distro) self.manifest.add_section('components') def load_local_policy(self): try: import sos.policies self.policy = sos.policies.load(sysroot=self.opts.sysroot, probe_runtime=self.load_probe) self.sysroot = self.policy.sysroot except KeyboardInterrupt: self._exit(0) self._is_root = self.policy.is_root() def execute(self): raise NotImplementedError def get_exit_handler(self): def exit_handler(signum, frame): # pylint: disable=unused-argument self.exit_process = True self._exit() return exit_handler def _exit(self, error=0, msg=None): if msg: self.ui_log.error("") self.ui_log.error(msg) raise SystemExit(error) def get_tmpdir_default(self): """If --tmp-dir is not specified, provide a default location. Normally this is /var/tmp, but if we detect we are in a container, then use a standardized env var to redirect to the host's filesystem instead """ if self.opts.tmp_dir: tmpdir = os.path.abspath(self.opts.tmp_dir) else: tmpdir = os.getenv('TMPDIR', None) or '/var/tmp' if os.getenv('HOST', None) and os.getenv('container', None): tmpdir = os.path.join(os.getenv('HOST'), tmpdir.lstrip('/')) # no standard library method exists for this, so call out to stat to # avoid bringing in a dependency on psutil self.tmpfstype = shell_out( f"stat --file-system --format=%T {tmpdir}" ).strip() if self.tmpfstype == 'tmpfs': # can't log to the ui or sos.log yet since those require a defined # tmpdir to setup print("WARNING: tmp-dir is set to a tmpfs filesystem. This may " "increase memory pressure and cause instability on low " "memory systems, or when using --all-logs.") time.sleep(2) return tmpdir def check_listing_options(self): opts = [o for o in self.opts.dict().keys() if o.startswith('list')] if opts: return any(getattr(self.opts, opt) for opt in opts) return False @classmethod def add_parser_options(cls, parser): """This should be overridden by each subcommand to add its own unique options to the parser """ pass def apply_options_from_cmdline(self, opts): """(Re-)apply options specified via the cmdline to an options instance There are several cases where we may need to re-apply the options from the cmdline over previously loaded options - for instance when an option is specified in both a config file and cmdline, or a preset and the cmdline, or all three. Use this to re-apply cmdline option overrides to anything that may change the default values of options Positional arguments: :param opts: SoSOptions object to update """ # override the values from config files with the values from the # cmdline iff that value was explicitly specified, and compare it to # the _current_ set of opts from the config files as a default cmdopts = SoSOptions().from_args( self.parser.parse_args(self.cmdline), arg_defaults=opts.dict(preset_filter=False) ) # we can't use merge() here, as argparse will pass default values for # unset options which would effectively negate config file settings and # set all values back to their normal default codict = cmdopts.dict(preset_filter=False) for opt, val in codict.items(): if opt not in cmdopts.arg_defaults.keys() or val in [None, [], '']: continue # A plugin that is [enabled|disabled|only] in cmdopts must # overwrite these three options of itself in opts - reset it first if opt in ["enable_plugins", "skip_plugins", "only_plugins"]: for oopt in ["enable_plugins", "skip_plugins", "only_plugins"]: common = set(val) & set(getattr(opts, oopt)) # common has all plugins that are in this combination of # "[-e|-o|-n] plug" of cmdopts & "[-e|-o|-n] plug" of opts # so remove those plugins from this [-e|-o|-n] opts if common: setattr(opts, oopt, [x for x in getattr(opts, oopt) if x not in common]) if val != opts.arg_defaults[opt]: setattr(opts, opt, val) return opts def load_options(self): """Compile arguments loaded from defaults, config files, and the command line into a usable set of options """ # load the defaults defined by the component and the shared options opts = SoSOptions(arg_defaults=self._arg_defaults) for option in self.parser._actions: if option.default != SUPPRESS: option.default = None opts.update_from_conf(self.args.config_file, self.args.component) # directly check the cmdline options here as they have yet to be loaded # as SoSOptions, and if we do this check after they are loaded we would # need to do a second update from cmdline options for overriding config # file values if '--clean' in self.cmdline or '--mask' in self.cmdline: opts.update_from_conf(self.args.config_file, 'clean') if os.getuid() != 0: userconf = os.path.join(Path.home(), '.config/sos/sos.conf') if os.path.exists(userconf): opts.update_from_conf(userconf, self.args.component) opts = self.apply_options_from_cmdline(opts) # user specified command line preset self.preset = None if hasattr(opts, 'preset'): if opts.preset != self._arg_defaults["preset"]: self.preset = self.policy.find_preset(opts.preset) if not self.preset: sys.stderr.write(f"Unknown preset: '{opts.preset}'\n") self.preset = self.policy.probe_preset() opts.list_presets = True # --preset=auto if not self.preset: self.preset = self.policy.probe_preset() # now merge preset options to opts opts.merge(self.preset.opts) # re-apply any cmdline overrides to the preset opts = self.apply_options_from_cmdline(opts) if hasattr(self.preset.opts, 'verbosity') and \ self.preset.opts.verbosity > 0: self.set_loggers_verbosity(self.preset.opts.verbosity) return opts def cleanup(self): # archive and tempfile cleanup may fail due to a fatal # OSError exception (ENOSPC, EROFS etc.). try: if self.archive: self.archive.cleanup() if self.tempfile_util: self.tempfile_util.clean() if self.tmpdir: rmtree(self.tmpdir) except Exception as err: print(f"Failed to finish cleanup: {err}\nContents may remain in " f"{self.tmpdir}") def _set_encrypt_from_env_vars(self): msg = ('No encryption environment variables set, archive will not be ' 'encrypted') if os.environ.get('SOSENCRYPTKEY'): self.opts.encrypt_key = os.environ.get('SOSENCRYPTKEY') msg = 'Encryption key set via environment variable' elif os.environ.get('SOSENCRYPTPASS'): self.opts.encrypt_pass = os.environ.get('SOSENCRYPTPASS') msg = 'Encryption passphrase set via environment variable' self.soslog.info(msg) self.ui_log.info(msg) def _get_encryption_method(self): if not self.opts.batch: _enc = None while _enc not in ('P', 'K', 'E', 'N'): _enc = input(( 'Specify encryption method [P]assphrase, [K]ey, [E]nv ' 'vars, [N]o encryption: ' )).upper() if _enc == 'P': self.opts.encrypt_pass = getpass('Specify encryption ' 'passphrase: ') elif _enc == 'K': self.opts.encrypt_key = input('Specify encryption key: ') elif _enc == 'E': self._set_encrypt_from_env_vars() else: self.opts.encrypt_key = None self.opts.encrypt_pass = None self.soslog.info("User specified --encrypt, but chose no " "encryption when prompted.") self.ui_log.warning("Archive will not be encrypted") else: self._set_encrypt_from_env_vars() def setup_archive(self, name=''): if self.opts.encrypt: self._get_encryption_method() enc_opts = { 'encrypt': self.opts.encrypt_pass or self.opts.encrypt_key, 'key': self.opts.encrypt_key, 'password': self.opts.encrypt_pass } if not name: name = self.policy.get_archive_name() archive_name = os.path.join(self.tmpdir, name) if self.opts.compression_type == 'auto': auto_archive = self.policy.get_preferred_archive() self.archive = auto_archive(archive_name, self.tmpdir, self.policy, self.opts.threads, enc_opts, self.sysroot, self.manifest) else: self.archive = TarFileArchive(archive_name, self.tmpdir, self.policy, self.opts.threads, enc_opts, self.sysroot, self.manifest) self.archive.set_debug(self.opts.verbosity > 2) def _obfuscate_upload_passwords(self): # obfuscate strings like: # --upload-pass=PASSWORD # --upload-pass PASSWORD # --upload-url https://user:PASSWORD@some.url # in both sos_logs/sos.log and in sos_reports/manifest.json # and several sos_commands/* places from plugins's collected data _arc_path = self.archive.get_archive_path() for path in self.files_with_upload_passwd: for f in Path(_arc_path).glob(path): # get just the relative path that archive works with f = os.path.relpath(f, _arc_path) for re in [r"(--upload-pass[\s=]+)\S+", r"(--upload-url[\s=]+\S+://.*:)([^@]*)", r"(--upload-s3-secret-key[\s=]+)\S+"]: self.archive.do_file_sub(f, re, r"\1********") def add_ui_log_to_stdout(self): ui_console = logging.StreamHandler(sys.stdout) ui_console.setFormatter(logging.Formatter('%(message)s')) ui_console.setLevel( logging.DEBUG if self.opts.verbosity > 1 else logging.INFO ) self.ui_log.addHandler(ui_console) def set_loggers_verbosity(self, verbosity): if getattr(self, 'flog', None) and verbosity: self.flog.setLevel(logging.DEBUG) if getattr(self, 'console', None): if verbosity and self.opts.verbosity > 1: self.console.setLevel(logging.DEBUG) else: self.console.setLevel(logging.WARNING) def _setup_logging(self): """Creates the log handler that shall be used by all components and any and all related bits to those components that need to log either to the console or to the log file for that run of sos. """ # main soslog self.soslog = logging.getLogger('sos') self.soslog.setLevel(logging.DEBUG) self.flog = None if not self.check_listing_options(): self.sos_log_file = self.get_temp_file() self.flog = logging.StreamHandler(self.sos_log_file) self.flog.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s')) self.flog.setLevel(logging.INFO) self.soslog.addHandler(self.flog) if not self.opts.quiet: self.console = logging.StreamHandler(sys.stdout) self.console.setFormatter(logging.Formatter('%(message)s')) self.set_loggers_verbosity(self.opts.verbosity) self.soslog.addHandler(self.console) # still log ERROR level message to console, but only setup this handler # when --quiet is used, as otherwise we'll double log else: console_err = logging.StreamHandler(sys.stderr) console_err.setFormatter(logging.Formatter('%(message)s')) console_err.setLevel(logging.ERROR) self.soslog.addHandler(console_err) # ui log self.ui_log = logging.getLogger('sos_ui') self.ui_log.setLevel( logging.DEBUG if self.opts.verbosity > 1 else logging.INFO ) if not self.check_listing_options(): self.sos_ui_log_file = self.get_temp_file() ui_fhandler = logging.StreamHandler(self.sos_ui_log_file) ui_fhandler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s')) self.ui_log.addHandler(ui_fhandler) if not self.opts.quiet: self.add_ui_log_to_stdout() def get_temp_file(self): return self.tempfile_util.new() class SoSMetadata(): """This class is used to record metadata from a sos execution that will then be stored as a JSON-formatted manifest within the final tarball. It can be extended by adding further instances of SoSMetadata to represent dict-like structures throughout the various sos bits that record to metadata """ def __init__(self): self._values = {} def __iter__(self): for item in self._values.items(): yield item[1] def __getitem__(self, item): return self._values[item] def __getattr__(self, attr): try: return self._values[attr] except Exception: raise AttributeError(attr) def add_field(self, field_name, content): """Add a key, value entry to the current metadata instance """ self._values[field_name] = content def add_section(self, section_name): """Adds a new instance of SoSMetadata to the current instance """ self._values[section_name] = SoSMetadata() return self._values[section_name] def add_list(self, list_name, content=[]): """Add a named list element to the current instance. If content is not supplied, then add an empty list that can alter be appended to """ if not isinstance(content, list): raise TypeError('content added must be list') self._values[list_name] = content def get_json(self, indent=None): """Convert contents of this SoSMetdata instance, and all other nested instances (sections), into a json-formatted output. Used to write manifest.json to the final archives. """ return json.dumps(self, default=lambda o: getattr(o, '_values', str(o)), indent=indent) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/0000775000175000017500000000000014660147624013204 5ustar arifarifsos-4.8.0/sos/cleaner/__init__.py0000664000175000017500000012202414660147624015316 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import hashlib import json import logging import os import shutil import tempfile import fnmatch from concurrent.futures import ThreadPoolExecutor from datetime import datetime from pwd import getpwuid from textwrap import fill import sos.cleaner.preppers from sos import __version__ from sos.component import SoSComponent from sos.cleaner.parsers.ip_parser import SoSIPParser from sos.cleaner.parsers.mac_parser import SoSMacParser from sos.cleaner.parsers.hostname_parser import SoSHostnameParser from sos.cleaner.parsers.keyword_parser import SoSKeywordParser from sos.cleaner.parsers.username_parser import SoSUsernameParser from sos.cleaner.parsers.ipv6_parser import SoSIPv6Parser from sos.cleaner.archives.sos import (SoSReportArchive, SoSReportDirectory, SoSCollectorArchive, SoSCollectorDirectory) from sos.cleaner.archives.generic import DataDirArchive, TarballArchive from sos.cleaner.archives.insights import InsightsArchive from sos.utilities import get_human_readable, import_module, ImporterHelper class SoSCleaner(SoSComponent): """ This function is designed to obfuscate potentially sensitive information from an sos report archive in a consistent and reproducible manner. It may either be invoked during the creation of a report by using the --clean option in the report command, or may be used on an already existing archive by way of 'sos clean'. The target of obfuscation are items such as IP addresses, MAC addresses, hostnames, usernames, and also keywords provided by users via the --keywords and/or --keyword-file options. For every collection made in a report the collection is parsed for such items, and when items are found SoS will generate an obfuscated replacement for it, and in all places that item is found replace the text with the obfuscated replacement mapped to it. These mappings are saved locally so that future iterations will maintain the same consistent obfuscation pairing. In the case of IP addresses, support is for IPv4 and IPv6 - effort is made to keep network topology intact so that later analysis is as accurate and easily understandable as possible. If an IP address is encountered that we cannot determine the netmask for, a random IP address is used instead. For IPv6, note that IPv4-mapped addresses, e.g. ::ffff:10.11.12.13, are NOT supported currently, and will remain unobfuscated. For hostnames, domains are obfuscated as whole units, leaving the TLD in place. For instance, 'example.com' may be obfuscated to 'obfuscateddomain0.com' and 'foo.example.com' may end up being 'obfuscateddomain1.com'. Users will be notified of a 'mapping' file that records all items and the obfuscated counterpart mapped to them for ease of reference later on. This file should be kept private. """ desc = "Obfuscate sensitive networking information in a report" arg_defaults = { 'archive_type': 'auto', 'domains': [], 'disable_parsers': [], 'skip_cleaning_files': [], 'jobs': 4, 'keywords': [], 'keyword_file': None, 'map_file': '/etc/sos/cleaner/default_mapping', 'no_update': False, 'keep_binary_files': False, 'target': '', 'usernames': [] } def __init__(self, parser=None, args=None, cmdline=None, in_place=False, hook_commons=None): if not in_place: # we are running `sos clean` directly super().__init__(parser, args, cmdline) self.from_cmdline = True else: # we are being hooked by either SoSReport or SoSCollector, don't # re-init everything as that will cause issues, but instead load # the needed bits from the calling component self.opts = hook_commons['options'] self.tmpdir = hook_commons['tmpdir'] self.sys_tmp = hook_commons['sys_tmp'] self.policy = hook_commons['policy'] self.manifest = hook_commons['manifest'] self.from_cmdline = False if not hasattr(self.opts, 'jobs'): self.opts.jobs = 4 self.opts.archive_type = 'auto' self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') # create the tmp subdir here to avoid a potential race condition # when obfuscating a SoSCollector run during archive extraction os.makedirs(os.path.join(self.tmpdir, 'cleaner'), exist_ok=True) self.review_parser_values() self.cleaner_mapping = self.load_map_file() os.umask(0o77) self.in_place = in_place self.hash_name = self.policy.get_preferred_hash_name() self.cleaner_md = self.manifest.components.add_section('cleaner') skip_cleaning_files = self.opts.skip_cleaning_files self.parsers = [ SoSHostnameParser(self.cleaner_mapping, skip_cleaning_files), SoSIPParser(self.cleaner_mapping, skip_cleaning_files), SoSIPv6Parser(self.cleaner_mapping, skip_cleaning_files), SoSMacParser(self.cleaner_mapping, skip_cleaning_files), SoSKeywordParser(self.cleaner_mapping, skip_cleaning_files), SoSUsernameParser(self.cleaner_mapping, skip_cleaning_files) ] for _parser in self.opts.disable_parsers: for _loaded in self.parsers: _temp = _loaded.name.lower().split('parser', maxsplit=1)[0] _loaded_name = _temp.strip() if _parser.lower().strip() == _loaded_name: self.log_info(f"Disabling parser: {_loaded_name}") self.ui_log.warning( f"Disabling the '{_parser}' parser. Be aware that this" " may leave sensitive plain-text data in the archive." ) self.parsers.remove(_loaded) self.archive_types = [ SoSReportDirectory, SoSReportArchive, SoSCollectorDirectory, SoSCollectorArchive, InsightsArchive, # make sure these two are always last as they are fallbacks DataDirArchive, TarballArchive ] self.nested_archive = None self.log_info( f"Cleaner initialized. From cmdline: {self.from_cmdline}") def _fmt_log_msg(self, msg, caller=None): return f"[cleaner{f':{caller}' if caller else ''}] {msg}" def log_debug(self, msg, caller=None): self.soslog.debug(self._fmt_log_msg(msg, caller)) def log_info(self, msg, caller=None): self.soslog.info(self._fmt_log_msg(msg, caller)) def log_error(self, msg, caller=None): self.soslog.error(self._fmt_log_msg(msg, caller)) def _fmt_msg(self, msg): width = 80 _fmt = '' for line in msg.splitlines(): _fmt = _fmt + fill(line, width, replace_whitespace=False) + '\n' return _fmt @classmethod def display_help(cls, section): section.set_title("SoS Cleaner Detailed Help") section.add_text(cls.__doc__) def load_map_file(self): """Verifies that the map file exists and has usable content. If the provided map file does not exist, or it is empty, we will print a warning and continue on with cleaning building a fresh map """ _conf = {} default_map = '/etc/sos/cleaner/default_mapping' if os.path.isdir(self.opts.map_file): raise Exception(f"Requested map file {self.opts.map_file} is a " "directory") if not os.path.exists(self.opts.map_file): if self.opts.map_file != default_map: self.log_error( f"ERROR: map file {self.opts.map_file} does not exist, " "will not load any obfuscation matches") else: with open(self.opts.map_file, 'r', encoding='utf-8') as mf: try: _conf = json.load(mf) except json.JSONDecodeError: self.log_error("ERROR: Unable to parse map file, json is " "malformed. Will not load any mappings.") except Exception as err: self.log_error("ERROR: Could not load " f"'{self.opts.map_file}': {err}") return _conf def print_disclaimer(self): """When we are directly running `sos clean`, rather than hooking into SoSCleaner via report or collect, print a disclaimer banner """ msg = self._fmt_msg("""\ This command will attempt to obfuscate information that is generally \ considered to be potentially sensitive. Such information includes IP \ addresses, MAC addresses, domain names, and any user-provided keywords. Note that this utility provides a best-effort approach to data obfuscation, \ but it does not guarantee that such obfuscation provides complete coverage of \ all such data in the archive, or that any obfuscation is provided to data that\ does not fit the description above. Users should review any resulting data and/or archives generated or processed \ by this utility for remaining sensitive content before being passed to a \ third party. """) self.ui_log.info(f"\nsos clean (version {__version__})\n") self.ui_log.info(msg) if not self.opts.batch: try: input("\nPress ENTER to continue, or CTRL-C to quit.\n") except KeyboardInterrupt: self.ui_log.info("\nExiting on user cancel") self._exit(130) except Exception as e: self._exit(1, e) @classmethod def add_parser_options(cls, parser): parser.usage = 'sos clean|mask TARGET [options]' clean_grp = parser.add_argument_group( 'Cleaner/Masking Options', 'These options control how data obfuscation is performed' ) clean_grp.add_argument('target', metavar='TARGET', help='The directory or archive to obfuscate') clean_grp.add_argument('--archive-type', default='auto', choices=['auto', 'report', 'collect', 'insights', 'data-dir', 'tarball'], help=('Specify what kind of archive the target ' 'was generated as')) clean_grp.add_argument('--domains', action='extend', default=[], help='List of domain names to obfuscate') clean_grp.add_argument('--disable-parsers', action='extend', default=[], dest='disable_parsers', help=('Disable specific parsers, so that those ' 'elements are not obfuscated')) clean_grp.add_argument('--skip-cleaning-files', '--skip-masking-files', action='extend', default=[], dest='skip_cleaning_files', help=('List of files to skip/ignore during ' 'cleaning. Globs are supported.')) clean_grp.add_argument('-j', '--jobs', default=4, type=int, help='Number of concurrent archives to clean') clean_grp.add_argument('--keywords', action='extend', default=[], dest='keywords', help='List of keywords to obfuscate') clean_grp.add_argument('--keyword-file', default=None, dest='keyword_file', help='Provide a file a keywords to obfuscate') clean_grp.add_argument('--map-file', dest='map_file', default='/etc/sos/cleaner/default_mapping', help=('Provide a previously generated mapping ' 'file for obfuscation')) clean_grp.add_argument('--no-update', dest='no_update', default=False, action='store_true', help='Do not update the --map-file with new ' 'mappings from this run') clean_grp.add_argument('--keep-binary-files', default=False, action='store_true', dest='keep_binary_files', help='Keep unprocessable binary files in the ' 'archive instead of removing them') clean_grp.add_argument('--usernames', dest='usernames', default=[], action='extend', help='List of usernames to obfuscate') def set_target_path(self, path): """For use by report and collect to set the TARGET option appropriately so that execute() can be called just as if we were running `sos clean` directly from the cmdline. """ self.opts.target = path def inspect_target_archive(self): """The target path is not a directory, so inspect it for being an archive or an archive of archives. In the event the target path is not an archive, abort. """ _arc = None if self.opts.archive_type != 'auto': check_type = self.opts.archive_type.replace('-', '_') for archive in self.archive_types: if archive.type_name == check_type: _arc = archive(self.opts.target, self.tmpdir) else: for arc in self.archive_types: if arc.check_is_type(self.opts.target): _arc = arc(self.opts.target, self.tmpdir) break if not _arc: return self.report_paths.append(_arc) if _arc.is_nested: self.report_paths.extend(_arc.get_nested_archives()) # We need to preserve the top level archive until all # nested archives are processed self.report_paths.remove(_arc) self.nested_archive = _arc if self.nested_archive: self.nested_archive.ui_name = self.nested_archive.description def review_parser_values(self): """Check any values passed to the parsers via the commandline: - For the --domains option, ensure that they are valid for the parser in question. - Convert --skip-cleaning-files from globs to regular expressions. """ for _dom in self.opts.domains: if len(_dom.split('.')) < 2: raise Exception( f"Invalid value '{_dom}' given: --domains values must be " "actual domains" ) self.opts.skip_cleaning_files = [fnmatch.translate(p) for p in self.opts.skip_cleaning_files] def execute(self): """SoSCleaner will begin by inspecting the TARGET option to determine if it is a directory, archive, or archive of archives. In the case of a directory, the default behavior will be to edit the data in place. For an archive will we unpack the archive, iterate over the contents, and then repack the archive. In the case of an archive of archives, such as one from SoSCollector, each archive will be unpacked, cleaned, and repacked and the final top-level archive will then be repacked as well. """ self.arc_name = self.opts.target.split('/')[-1].split('.tar')[0] if self.from_cmdline: self.print_disclaimer() self.report_paths = [] if not os.path.exists(self.opts.target): self.ui_log.error("Invalid target: no such file or directory " f"{self.opts.target}") self._exit(1) self.inspect_target_archive() if not self.report_paths: self.ui_log.error("No valid archives or directories found\n") self._exit(1) # we have at least one valid target to obfuscate self.completed_reports = [] # TODO: as we separate mappings and parsers further, do this in a less # janky manner for parser in self.parsers: if parser.name == 'Hostname Parser': parser.mapping.set_initial_counts() self.preload_all_archives_into_maps() self.generate_parser_item_regexes() self.obfuscate_report_paths() if not self.completed_reports: if self.in_place: return None self.ui_log.info("No reports obfuscated, aborting...\n") self._exit(1) self.ui_log.info("\nSuccessfully obfuscated " f"{len(self.completed_reports)} report(s)\n") _map = self.compile_mapping_dict() map_path = self.write_map_for_archive(_map) self.write_map_for_config(_map) self.write_stats_to_manifest() if self.in_place: arc_paths = [a.final_archive_path for a in self.completed_reports] return map_path, arc_paths final_path = None if len(self.completed_reports) > 1: arc_path = self.rebuild_nested_archive() else: arc = self.completed_reports[0] arc_path = arc.final_archive_path checksum = self.get_new_checksum(arc.final_archive_path) if checksum is not None: chksum_name = self.obfuscate_string( f"{arc_path.split('/')[-1]}.{self.hash_name}" ) with open(os.path.join(self.sys_tmp, chksum_name), 'w', encoding='utf-8') as cf: cf.write(checksum) self.write_cleaner_log() final_path = os.path.join( self.sys_tmp, self.obfuscate_string(arc_path.split('/')[-1]) ) shutil.move(arc_path, final_path) arcstat = os.stat(final_path) # while these messages won't be included in the log file in the archive # some facilities, such as our avocado test suite, will sometimes not # capture print() output, so leverage the ui_log to print to console self.ui_log.info( f"A mapping of obfuscated elements is available at\n\t{map_path}" ) self.ui_log.info( f"\nThe obfuscated archive is available at\n\t{final_path}\n" ) self.ui_log.info(f"\tSize\t{get_human_readable(arcstat.st_size)}") self.ui_log.info(f"\tOwner\t{getpwuid(arcstat.st_uid).pw_name}\n") self.ui_log.info("Please send the obfuscated archive to your support " "representative and keep the mapping file private") self.cleanup() return None def rebuild_nested_archive(self): """Handles repacking the nested tarball, now containing only obfuscated copies of the reports, log files, manifest, etc... """ # we have an archive of archives, so repack the obfuscated tarball arc_name = self.arc_name + '-obfuscated' self.setup_archive(name=arc_name) for archive in self.completed_reports: arc_dest = archive.final_archive_path.split('/')[-1] checksum = self.get_new_checksum(archive.final_archive_path) if checksum is not None: dname = f"checksums/{arc_dest}.{self.hash_name}" self.archive.add_string(checksum, dest=dname) for dirn, _, files in os.walk(self.nested_archive.extracted_path): for filename in files: fname = os.path.join(dirn, filename) dname = fname.split(self.nested_archive.extracted_path)[-1] dname = dname.lstrip('/') self.archive.add_file(fname, dest=dname) # remove it now so we don't balloon our fs space needs os.remove(fname) self.write_cleaner_log(archive=True) return self.archive.finalize(self.opts.compression_type) def compile_mapping_dict(self): """Build a dict that contains each parser's map as a key, with the contents as that key's value. This will then be written to disk in the same directory as the obfuscated report so that sysadmins have a way to 'decode' the obfuscation locally """ _map = {} for parser in self.parsers: _map[parser.map_file_key] = {} _map[parser.map_file_key].update(parser.get_map_contents()) return _map def write_map_to_file(self, _map, path): """Write the mapping to a file on disk that is in the same location as the final archive(s). """ with open(path, 'w', encoding='utf-8') as mf: mf.write(json.dumps(_map, indent=4)) return path def write_map_for_archive(self, _map): try: map_path = os.path.join( self.sys_tmp, self.obfuscate_string(f"{self.arc_name}-private_map") ) return self.write_map_to_file(_map, map_path) except Exception as err: self.log_error(f"Could not write private map file: {err}") return None def write_map_for_config(self, _map): """Write the mapping to the config file so that subsequent runs are able to provide the same consistent mapping """ if self.opts.map_file and not self.opts.no_update: cleaner_dir = os.path.dirname(self.opts.map_file) # Attempt to create the directory /etc/sos/cleaner # just in case it didn't exist previously try: os.makedirs(cleaner_dir, exist_ok=True) self.write_map_to_file(_map, self.opts.map_file) self.log_debug(f"Wrote mapping to {self.opts.map_file}") except Exception as err: self.log_error(f"Could not update mapping config file: {err}") def write_cleaner_log(self, archive=False): """When invoked via the command line, the logging from SoSCleaner will not be added to the archive(s) it processes, so we need to write it separately to disk """ log_name = os.path.join( self.sys_tmp, f"{self.arc_name}-obfuscation.log" ) with open(log_name, 'w', encoding='utf-8') as logfile: self.sos_log_file.seek(0) for line in self.sos_log_file.readlines(): logfile.write(line) if archive: self.obfuscate_file(log_name) self.archive.add_file(log_name, dest="sos_logs/cleaner.log") def get_new_checksum(self, archive_path): """Calculate a new checksum for the obfuscated archive, as the previous checksum will no longer be valid """ try: hash_size = 1024**2 # Hash 1MiB of content at a time. with open(archive_path, 'rb') as archive_fp: digest = hashlib.new(self.hash_name) while True: hashdata = archive_fp.read(hash_size) if not hashdata: break digest.update(hashdata) return digest.hexdigest() + '\n' except Exception as err: self.log_debug(f"Could not generate new checksum: {err}") return None def obfuscate_report_paths(self): """Perform the obfuscation for each archive or sos directory discovered during setup. Each archive is handled in a separate thread, up to self.opts.jobs will be obfuscated concurrently. """ try: msg = ( f"Found {len(self.report_paths)} total reports to obfuscate, " f"processing up to {self.opts.jobs} concurrently\n" ) self.ui_log.info(msg) if self.opts.keep_binary_files: self.ui_log.warning( "WARNING: binary files that potentially contain sensitive " "information will NOT be removed from the final archive\n" ) pool = ThreadPoolExecutor(self.opts.jobs) pool.map(self.obfuscate_report, self.report_paths, chunksize=1) pool.shutdown(wait=True) # finally, obfuscate the nested archive if one exists if self.nested_archive: self._replace_obfuscated_archives() self.obfuscate_report(self.nested_archive) except KeyboardInterrupt: self.ui_log.info("Exiting on user cancel") os._exit(130) def _replace_obfuscated_archives(self): """When we have a nested archive, we need to rebuild the original archive, which entails replacing the existing archives with their obfuscated counterparts """ for archive in self.completed_reports: os.remove(archive.archive_path) dest = self.nested_archive.extracted_path tarball = archive.final_archive_path.split('/')[-1] dest_name = os.path.join(dest, tarball) shutil.move(archive.final_archive_path, dest) archive.final_archive_path = dest_name def generate_parser_item_regexes(self): """For the parsers that use prebuilt lists of items, generate those regexes now since all the parsers should be preloaded by the archive(s) as well as being handed cmdline options and mapping file configuration. """ for parser in self.parsers: parser.generate_item_regexes() def _prepare_archive_with_prepper(self, archive, prepper): """ For each archive we've determined we need to operate on, pass it to each prepper so that we can extract necessary files and/or items for direct regex replacement. Preppers define these methods per parser, so it is possible that a single prepper will read the same file for different parsers/mappings. This is preferable to the alternative of building up monolithic lists of file paths, as we'd still need to manipulate these on a per-archive basis. :param archive: The archive we are currently using to prepare our mappings with :type archive: ``SoSObfuscationArchive`` subclass :param prepper: The individual prepper we're using to source items :type prepper: ``SoSPrepper`` subclass """ for _parser in self.parsers: pname = _parser.name.lower().split()[0].strip() for _file in prepper.get_parser_file_list(pname, archive): content = archive.get_file_content(_file) if not content: continue self.log_debug(f"Prepping {pname} parser with file {_file} " f"from {archive.ui_name}") for line in content.splitlines(): try: _parser.parse_line(line) except Exception as err: self.log_debug( f"Failed to prep {pname} map from {_file}: {err}" ) map_items = prepper.get_items_for_map(pname, archive) if map_items: self.log_debug(f"Prepping {pname} mapping with items from " f"{archive.ui_name}") for item in map_items: _parser.mapping.add(item) for ritem in prepper.regex_items[pname]: _parser.mapping.add_regex_item(ritem) def get_preppers(self): """ Discover all locally available preppers so that we can prepare the mappings with obfuscation matches in a controlled manner :returns: All preppers that can be leveraged locally :rtype: A generator of `SoSPrepper` items """ helper = ImporterHelper(sos.cleaner.preppers) preps = [] for _prep in helper.get_modules(): preps.extend(import_module(f"sos.cleaner.preppers.{_prep}")) for prepper in sorted(preps, key=lambda x: x.priority): yield prepper(options=self.opts) def preload_all_archives_into_maps(self): """Before doing the actual obfuscation, if we have multiple archives to obfuscate then we need to preload each of them into the mappings to ensure that node1 is obfuscated in node2 as well as node2 being obfuscated in node1's archive. """ self.log_info("Pre-loading all archives into obfuscation maps") for prepper in self.get_preppers(): for archive in self.report_paths: self._prepare_archive_with_prepper(archive, prepper) def obfuscate_report(self, archive): # pylint: disable=too-many-branches """Individually handle each archive or directory we've discovered by running through each file therein. Positional arguments: :param report str: Filepath to the directory or archive """ try: arc_md = self.cleaner_md.add_section(archive.archive_name) start_time = datetime.now() arc_md.add_field('start_time', start_time) # don't double extract nested archives if not archive.is_extracted: archive.extract() archive.report_msg("Beginning obfuscation...") for fname in archive.get_file_list(): short_name = fname.split(archive.archive_name + '/')[1] if archive.should_skip_file(short_name): continue if (not self.opts.keep_binary_files and archive.should_remove_file(short_name)): archive.remove_file(short_name) continue try: count = self.obfuscate_file(fname, short_name, archive.archive_name) if count: archive.update_sub_count(short_name, count) except Exception as err: self.log_debug(f"Unable to parse file {short_name}: {err}") try: self.obfuscate_directory_names(archive) except Exception as err: self.log_info(f"Failed to obfuscate directories: {err}", caller=archive.archive_name) try: self.obfuscate_symlinks(archive) except Exception as err: self.log_info(f"Failed to obfuscate symlinks: {err}", caller=archive.archive_name) # if the archive was already a tarball, repack it if not archive.is_nested: method = archive.get_compression() if method: archive.report_msg("Re-compressing...") try: archive.rename_top_dir( self.obfuscate_string(archive.archive_name) ) archive.compress(method) except Exception as err: self.log_debug(f"Archive {archive.archive_name} failed" f" to compress: {err}") archive.report_msg( f"Failed to re-compress archive: {err}") return self.completed_reports.append(archive) end_time = datetime.now() arc_md.add_field('end_time', end_time) arc_md.add_field('run_time', end_time - start_time) arc_md.add_field('files_obfuscated', len(archive.file_sub_list)) arc_md.add_field('total_substitutions', archive.total_sub_count) rmsg = '' if archive.removed_file_count: rmsg = " [removed %s unprocessable files]" rmsg = rmsg % archive.removed_file_count archive.report_msg(f"Obfuscation completed{rmsg}") except Exception as err: self.ui_log.info("Exception while processing " f"{archive.archive_name}: {err}") def obfuscate_file(self, filename, short_name=None, arc_name=None): # pylint: disable=too-many-locals """Obfuscate and individual file, line by line. Lines processed, even if no substitutions occur, are then written to a temp file without our own tmpdir. Once the file has been completely iterated through, if there have been substitutions then the temp file overwrites the original file. If there are no substitutions, then the original file is left in place. Positional arguments: :param filename str: Filename relative to the extracted archive root """ if not filename: # the requested file doesn't exist in the archive return None subs = 0 if not short_name: short_name = filename.split('/')[-1] if not os.path.islink(filename): # don't run the obfuscation on the link, but on the actual file # at some other point. _parsers = [ _p for _p in self.parsers if not any( _skip.match(short_name) for _skip in _p.skip_patterns ) ] if not _parsers: self.log_debug( f"Skipping obfuscation of {short_name or filename} due to " f"matching file skip pattern" ) return 0 self.log_debug(f"Obfuscating {short_name or filename}", caller=arc_name) with tempfile.NamedTemporaryFile(mode='w', dir=self.tmpdir) \ as tfile: with open(filename, 'r', encoding='utf-8', errors='replace') as fname: for line in fname: try: line, count = self.obfuscate_line(line, _parsers) subs += count tfile.write(line) except Exception as err: self.log_debug(f"Unable to obfuscate {short_name}:" f"{err}", caller=arc_name) tfile.seek(0) if subs: shutil.copyfile(tfile.name, filename) _ob_short_name = self.obfuscate_string(short_name.split('/')[-1]) _ob_filename = short_name.replace(short_name.split('/')[-1], _ob_short_name) if _ob_filename != short_name: arc_path = filename.split(short_name)[0] _ob_path = os.path.join(arc_path, _ob_filename) # ensure that any plugin subdirs that contain obfuscated strings # get created with obfuscated counterparts if not os.path.islink(filename): os.rename(filename, _ob_path) else: # generate the obfuscated name of the link target _target_ob = self.obfuscate_string(os.readlink(filename)) # remove the unobfuscated original symlink first, in case the # symlink name hasn't changed but the target has os.remove(filename) # create the newly obfuscated symlink, pointing to the # obfuscated target name, which may not exist just yet, but # when the actual file is obfuscated, will be created os.symlink(_target_ob, _ob_path) return subs def obfuscate_symlinks(self, archive): """Iterate over symlinks in the archive and obfuscate their names. The content of the link target will have already been cleaned, and this second pass over just the names of the links is to ensure we avoid a possible race condition dependent on the order in which the link or the target get obfuscated. :param archive: The archive being obfuscated :type archive: ``SoSObfuscationArchive`` """ self.log_info("Obfuscating symlink names", caller=archive.archive_name) for symlink in archive.get_symlinks(): try: # relative name of the symlink in the archive _sym = symlink.split(archive.extracted_path)[1].lstrip('/') # don't obfuscate symlinks for files that we skipped the first # obfuscation of, as that would create broken links _parsers = [ _p for _p in self.parsers if not any(_skip.match(_sym) for _skip in _p.skip_patterns) ] if not _parsers: self.log_debug( f"Skipping obfuscation of symlink {_sym} due to skip " f"pattern match" ) continue self.log_debug(f"Obfuscating symlink {_sym}", caller=archive.archive_name) # current target of symlink, again relative to the archive _target = os.readlink(symlink) # get the potentially renamed symlink name, this time the full # path as it exists on disk _ob_sym_name = os.path.join(archive.extracted_path, self.obfuscate_string(_sym)) # get the potentially renamed relative target filename _ob_target = self.obfuscate_string(_target) # if either the symlink name or the target name has changed, # recreate the symlink if (_ob_sym_name != symlink) or (_ob_target != _target): os.remove(symlink) os.symlink(_ob_target, _ob_sym_name) except Exception as err: self.log_info(f"Error obfuscating symlink '{symlink}': {err}") def obfuscate_directory_names(self, archive): """For all directories that exist within the archive, obfuscate the directory name if it contains sensitive strings found during execution """ self.log_info("Obfuscating directory names in archive " f"{archive.archive_name}") for dirpath in sorted(archive.get_directory_list(), reverse=True): for _name in os.listdir(dirpath): _dirname = os.path.join(dirpath, _name) _arc_dir = _dirname.split(archive.extracted_path)[-1] if os.path.isdir(_dirname): _ob_dirname = self.obfuscate_string(_name) if _ob_dirname != _name: _ob_arc_dir = _arc_dir.rstrip(_name) _ob_arc_dir = os.path.join( archive.extracted_path, _ob_arc_dir.lstrip('/'), _ob_dirname ) os.rename(_dirname, _ob_arc_dir) def obfuscate_string(self, string_data): for parser in self.parsers: try: string_data = parser.parse_string_for_keys(string_data) except Exception as err: self.log_info(f"Error obfuscating string data: {err}") return string_data def obfuscate_line(self, line, parsers=None): """Run a line through each of the obfuscation parsers, keeping a cumulative total of substitutions done on that particular line. Positional arguments: :param line str: The raw line as read from the file being processed :param parsers: A list of parser objects to obfuscate with. If None, use all. Returns the fully obfuscated line and the number of substitutions made """ # don't iterate over blank lines, but still write them to the tempfile # to maintain the same structure when we write a scrubbed file back count = 0 if not line.strip(): return line, count if parsers is None: parsers = self.parsers for parser in parsers: try: line, _count = parser.parse_line(line) count += _count except Exception as err: self.log_debug(f"failed to parse line: {err}", parser.name) return line, count def write_stats_to_manifest(self): """Write some cleaner-level, non-report-specific stats to the manifest """ parse_sec = self.cleaner_md.add_section('parsers') for parser in self.parsers: _sec = parse_sec.add_section(parser.name.replace(' ', '_').lower()) _sec.add_field('entries', len(parser.mapping.dataset.keys())) # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/preppers/0000775000175000017500000000000014660147624015044 5ustar arifarifsos-4.8.0/sos/cleaner/preppers/ip.py0000664000175000017500000000203214660147624016023 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.preppers import SoSPrepper class IPPrepper(SoSPrepper): """ This prepper is for IP network addresses. The aim of this prepper is to provide the file path for where the output of `ip addr` is saved. """ name = 'ip' def _get_ipv6_file_list(self, archive): return self._get_ip_file_list(archive) def _get_ip_file_list(self, archive): _files = [] if archive.is_sos: _files = ['sos_commands/networking/ip_-o_addr'] elif archive.is_insights: _files = ['data/insights_commands/ip_addr'] return _files # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/preppers/__init__.py0000664000175000017500000001152714660147624017163 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import logging class SoSPrepper(): """ A prepper is a way to prepare loaded mappings with selected items within an sos report prior to beginning the full obfuscation routine. This was previously handled directly within archives, however this is a bit cumbersome and doesn't allow for all the flexibility we could use in this effort. Preppers are separated from parsers but will leverage them in order to feed parser-matched strings from files highlighted by a Prepper() to the appropriate mapping for initial obfuscation. Preppers may specify their own priority in order to influence the order in which mappings are prepped. Further, Preppers have two ways to prepare the maps - either by generating a list of filenames or via directly pulling content out of select files without the assistance of a parser. A lower priority value means the prepper should run sooner than those with higher values. For the former approach, `Prepper._get_$parser_file_list()` should be used and should yield filenames that exist in target archives. For the latter, the `Prepper._get_items_for_$map()` should be used. Finally, a `regex_items` dict is available for storing individual regex items for parsers that rely on them. These items will be added after all files and other individual items are handled. This dict has keys set to parser/mapping names, and the values should be sets of items, so preppers should add to them like so: self.regex_items['hostname'].add('myhostname') """ name = 'Undefined' priority = 100 def __init__(self, options): self.regex_items = { 'hostname': set(), 'ip': set(), 'ipv6': set(), 'keyword': set(), 'mac': set(), 'username': set() } self.opts = options self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') def _fmt_log_msg(self, msg): return f"[prepper:{self.name}] {msg}" def log_debug(self, msg): self.soslog.debug(self._fmt_log_msg(msg)) def log_info(self, msg): self.soslog.info(self._fmt_log_msg(msg)) def log_error(self, msg): self.soslog.error(self._fmt_log_msg(msg)) def get_parser_file_list(self, parser, archive): """ Helper that calls the appropriate Prepper method for the specified parser. This allows Preppers to be able to provide items for multiple types of parsers without needing to handle repetitious logic to determine which parser we're interested within each individual call. The convention to use is to define `_get_$parser_file_list()` methods within Preppers, e.g. `_get_hostname_file_list()` would be used to provide filenames for the hostname parser. If such a method is not defined within a Prepper for a given parser, we handle that here so that individual Preppers do not need to. :param parser: The _name_ of the parser to get a file list for :type parser: ``str`` :param archive: The archive we are operating on currently for the specified parser :type archive: ``SoSObfuscationArchive`` :returns: A list of filenames within the archive to prep with :rtype: ``list`` """ _check = f"_get_{parser}_file_list" if hasattr(self, _check): return getattr(self, _check)(archive) return [] def get_items_for_map(self, mapping, archive): """ Similar to `get_parser_file_list()`, a helper for calling the specific method for generating items for the given `map`. This allows Preppers to be able to provide items for multiple types of maps, without the need to handle repetitious logic to determine which parser we're interested in within each individual call. :param mapping: The _name_ of the mapping to get items for :type mapping: ``str`` :param archive: The archive we are operating on currently for the specified parser :type archive: ``SoSObfuscationArchive`` :returns: A list of distinct items to obfuscate without using a parser :rtype: ``list`` """ _check = f"_get_items_for_{mapping}" if hasattr(self, _check): return getattr(self, _check)(archive) return [] # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/preppers/usernames.py0000664000175000017500000000423114660147624017420 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.preppers import SoSPrepper class UsernamePrepper(SoSPrepper): """ This prepper is used to source usernames from various `last` output content as well as a couple select files. This prepper will also leverage the --usernames option. """ name = 'username' skip_list = [ 'core', 'nobody', 'nfsnobody', 'shutdown', 'stack', 'reboot', 'root', 'ubuntu', 'username', 'wtmp' ] def _get_items_for_username(self, archive): items = set() _files = [ 'sos_commands/login/lastlog_-u_1000-60000', 'sos_commands/login/lastlog_-u_60001-65536', 'sos_commands/login/lastlog_-u_65537-4294967295', # AD users will be reported here, but favor the lastlog files since # those will include local users who have not logged in 'sos_commands/login/last', 'etc/cron.allow', 'etc/cron.deny' ] for _file in _files: content = archive.get_file_content(_file) if not content: continue for line in content.splitlines(): try: user = line.split()[0].lower() if user and user not in self.skip_list: items.add(user) if '\\' in user: items.add(user.split('\\')[-1]) except Exception: # empty line or otherwise unusable for name sourcing pass for opt_user in self.opts.usernames: if opt_user not in self.skip_list: items.add(opt_user) return items # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/preppers/mac.py0000664000175000017500000000154114660147624016157 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.preppers import SoSPrepper class MacPrepper(SoSPrepper): """ Prepper for sourcing the host's MAC address in order to prep the mapping. """ name = 'mac' def _get_mac_file_list(self, archive): if archive.is_sos: return ['sos_commands/networking/ip_-d_address'] if archive.is_insights: return ['data/insights_commands/ip_addr'] return [] # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/preppers/hostname.py0000664000175000017500000000432514660147624017240 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.preppers import SoSPrepper class HostnamePrepper(SoSPrepper): """ Prepper for providing domain and hostname information to the hostname mapping. The items from hostname sources are handled manually via the _get_items method, rather than passing the file directly, as the parser does not know what hostnames or domains to match on initially. This will also populate the regex_items list with local short names. """ name = 'hostname' def _get_items_for_hostname(self, archive): items = [] _file = 'hostname' if archive.is_sos: _file = 'sos_commands/host/hostname' elif archive.is_insights: _file = 'data/insights_commands/hostname_-f' content = archive.get_file_content(_file) if content and content != 'localhost': domains = content.split('.') if len(domains) > 1: items.append(domains[0]) self.regex_items['hostname'].add((domains[0])) if len(domains) > 3: # make sure we get example.com if the system's hostname # is something like foo.bar.example.com top_domain = '.'.join(domains[-2:]) items.append(top_domain.strip()) items.append(content.strip()) _hosts = archive.get_file_content('etc/hosts') for line in _hosts.splitlines(): if line.startswith('#') or 'localhost' in line: continue hostln = line.split()[1:] for host in hostln: if len(host.split('.')) == 1: self.regex_items['hostname'].add(host) else: items.append(host) for domain in self.opts.domains: items.append(domain) return items sos-4.8.0/sos/cleaner/preppers/keywords.py0000664000175000017500000000221114660147624017261 0ustar arifarif# Copyright 2023 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os from sos.cleaner.preppers import SoSPrepper class KeywordPrepper(SoSPrepper): """ Prepper to handle keywords passed to cleaner via either the `--keywords` or `--keyword-file` options. """ name = 'keyword' # pylint: disable=unused-argument def _get_items_for_keyword(self, archive): items = [] for kw in self.opts.keywords: items.append(kw) if self.opts.keyword_file and os.path.exists(self.opts.keyword_file): with open(self.opts.keyword_file, 'r', encoding='utf-8') as kwf: items.extend(kwf.read().splitlines()) for item in items: self.regex_items['keyword'].add(item) return items # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/archives/0000775000175000017500000000000014660147624015010 5ustar arifarifsos-4.8.0/sos/cleaner/archives/__init__.py0000664000175000017500000003751314660147624017132 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import logging import os import shutil import stat import tarfile import re from concurrent.futures import ProcessPoolExecutor from sos.utilities import file_is_binary # python older than 3.8 will hit a pickling error when we go to spawn a new # process for extraction if this method is a part of the SoSObfuscationArchive # class. So, the simplest solution is to remove it from the class. def extract_archive(archive_path, tmpdir): with tarfile.open(archive_path) as archive: path = os.path.join(tmpdir, 'cleaner') # set extract filter since python 3.12 (see PEP-706 for more) # Because python 3.10 and 3.11 raises false alarms as exceptions # (see #3330 for examples), we can't use data filter but must # fully trust the archive (legacy behaviour) archive.extraction_filter = getattr(tarfile, 'fully_trusted_filter', (lambda member, path: member)) # Guard against "Arbitrary file write during tarfile extraction" # Checks the extracted files don't stray out of the target directory. for member in archive.getmembers(): member_path = os.path.join(path, member.name) abs_directory = os.path.abspath(path) abs_target = os.path.abspath(member_path) prefix = os.path.commonprefix([abs_directory, abs_target]) if prefix != abs_directory: raise Exception(f"Attempted path traversal in tarfle" f"{prefix} != {abs_directory}") archive.extract(member, path) return os.path.join(path, archive.name.split('/')[-1].split('.tar')[0]) class SoSObfuscationArchive(): """A representation of an extracted archive or an sos archive build directory which is used by SoSCleaner. Each archive that needs to be obfuscated is loaded into an instance of this class. All report-level operations should be contained within this class. """ file_sub_list = [] total_sub_count = 0 removed_file_count = 0 type_name = 'undetermined' description = 'undetermined' is_nested = False prep_files = {} def __init__(self, archive_path, tmpdir): self.archive_path = archive_path self.final_archive_path = self.archive_path self.tmpdir = tmpdir self.archive_name = self.archive_path.split('/')[-1].split('.tar')[0] self.ui_name = self.archive_name self.soslog = logging.getLogger('sos') self.ui_log = logging.getLogger('sos_ui') self.skip_list = self._load_skip_list() self.is_extracted = False self._load_self() self.archive_root = '' self.log_info( f"Loaded {self.archive_path} as type {self.description}" ) @classmethod def check_is_type(cls, arc_path): """Check if the archive is a well-known type we directly support""" raise NotImplementedError @property def is_sos(self): return 'sos' in self.__class__.__name__.lower() @property def is_insights(self): return 'insights' in self.type_name def _load_self(self): if self.is_tarfile: # pylint: disable=consider-using-with self.tarobj = tarfile.open(self.archive_path) def get_nested_archives(self): """Return a list of ObfuscationArchives that represent additional archives found within the target archive. For example, an archive from `sos collect` will return a list of ``SoSReportArchive`` objects. This should be overridden by individual types of ObfuscationArchive's """ return [] def get_archive_root(self): """Set the root path for the archive that should be prepended to any filenames given to methods in this class. """ if self.is_tarfile: toplevel = self.tarobj.firstmember if toplevel.isdir(): return toplevel.name return os.path.dirname(toplevel.name) or os.sep return os.path.abspath(self.archive_path) def report_msg(self, msg): """Helper to easily format ui messages on a per-report basis""" self.ui_log.info(f"{self.ui_name + ' :':<50} {msg}") def _fmt_log_msg(self, msg): return f"[cleaner:{self.archive_name}] {msg}" def log_debug(self, msg): self.soslog.debug(self._fmt_log_msg(msg)) def log_info(self, msg): self.soslog.info(self._fmt_log_msg(msg)) def _load_skip_list(self): """Provide a list of files and file regexes to skip obfuscation on Returns: list of files and file regexes """ return [ 'proc/kallsyms', 'sosreport-', 'sys/firmware', 'sys/fs', 'sys/kernel/debug', 'sys/module' ] @property def is_tarfile(self): try: return tarfile.is_tarfile(self.archive_path) except Exception: return False def remove_file(self, fname): """Remove a file from the archive. This is used when cleaner encounters a binary file, which we cannot reliably obfuscate. """ full_fname = self.get_file_path(fname) # don't call a blank remove() here if full_fname: self.log_info(f"Removing binary file '{fname}' from archive") os.remove(full_fname) self.removed_file_count += 1 def format_file_name(self, fname): """Based on the type of archive we're dealing with, do whatever that archive requires to a provided **relative** filepath to be able to access it within the archive """ if not self.is_extracted: if not self.archive_root: self.archive_root = self.get_archive_root() return os.path.join(self.archive_root, fname) return os.path.join(self.extracted_path, fname) def get_file_content(self, fname): """Return the content from the specified fname. Particularly useful for tarball-type archives so we can retrieve prep file contents prior to extracting the entire archive """ if self.is_extracted is False and self.is_tarfile: filename = self.format_file_name(fname) try: return self.tarobj.extractfile(filename).read().decode('utf-8') except KeyError: self.log_debug( f"Unable to retrieve {fname}: no such file in archive" ) return '' else: try: with open(self.format_file_name(fname), 'r', encoding='utf-8') as to_read: return to_read.read() except Exception as err: self.log_debug(f"Failed to get contents of {fname}: {err}") return '' def extract(self, quiet=False): if self.is_tarfile: if not quiet: self.report_msg("Extracting...") self.extracted_path = self.extract_self() self.is_extracted = True else: self.extracted_path = self.archive_path # if we're running as non-root (e.g. collector), then we can have a # situation where a particular path has insufficient permissions for # us to rewrite the contents and/or add it to the ending tarfile. # Unfortunately our only choice here is to change the permissions # that were preserved during report collection if os.getuid() != 0: self.log_debug('Verifying permissions of archive contents') for dirname, dirs, files in os.walk(self.extracted_path): try: for _dir in dirs: _dirname = os.path.join(dirname, _dir) _dir_perms = os.stat(_dirname).st_mode os.chmod(_dirname, _dir_perms | stat.S_IRWXU) for filename in files: fname = os.path.join(dirname, filename) # protect against symlink race conditions if not os.path.exists(fname) or os.path.islink(fname): continue if (not os.access(fname, os.R_OK) or not os.access(fname, os.W_OK)): self.log_debug( "Adding owner rw permissions to " f"{fname.split(self.archive_path)[-1]}" ) os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR) except Exception as err: self.log_debug(f"Error while trying to set perms: {err}") self.log_debug(f"Extracted path is {self.extracted_path}") def rename_top_dir(self, new_name): """Rename the top-level directory to new_name, which should be an obfuscated string that scrubs the hostname from the top-level dir which would be named after the unobfuscated sos report """ _path = self.extracted_path.replace(self.archive_name, new_name) self.archive_name = new_name os.rename(self.extracted_path, _path) self.extracted_path = _path def get_compression(self): """Return the compression type used by the archive, if any. This is then used by SoSCleaner to generate a policy-derived compression command to repack the archive """ if self.is_tarfile: if self.archive_path.endswith('xz'): return 'xz' return 'gz' return None def build_tar_file(self, method): """Pack the extracted archive as a tarfile to then be re-compressed """ mode = 'w' tarpath = self.extracted_path + '-obfuscated.tar' compr_args = {} if method: mode += f":{method}" tarpath += f".{method}" if method == 'xz': compr_args = {'preset': 3} else: compr_args = {'compresslevel': 6} self.log_debug(f"Building tar file {tarpath}") with tarfile.open(tarpath, mode=mode, **compr_args) as tar: tar.add(self.extracted_path, arcname=os.path.split(self.archive_name)[1]) return tarpath def compress(self, method): """Execute the compression command, and set the appropriate final archive path for later reference by SoSCleaner on a per-archive basis """ try: self.final_archive_path = self.build_tar_file(method) except Exception as err: self.log_debug(f"Exception while re-compressing archive: {err}") raise self.log_debug(f"Compressed to {self.final_archive_path}") try: self.remove_extracted_path() except Exception as err: self.log_debug(f"Failed to remove extraction directory: {err}") self.report_msg('Failed to remove temporary extraction directory') def remove_extracted_path(self): """After the tarball has been re-compressed, remove the extracted path so that we don't take up that duplicate space any longer during execution """ try: self.log_debug(f"Removing {self.extracted_path}") shutil.rmtree(self.extracted_path) except OSError: os.chmod(self.extracted_path, stat.S_IWUSR) if os.path.isfile(self.extracted_path): os.remove(self.extracted_path) else: shutil.rmtree(self.extracted_path) def extract_self(self): """Extract an archive into our tmpdir so that we may inspect it or iterate through its contents for obfuscation """ with ProcessPoolExecutor(1) as _pool: _path_future = _pool.submit(extract_archive, self.archive_path, self.tmpdir) path = _path_future.result() return path def get_symlinks(self): """Iterator for a list of symlinks in the archive""" for dirname, dirs, files in os.walk(self.extracted_path): for _dir in dirs: _dirpath = os.path.join(dirname, _dir) if os.path.islink(_dirpath): yield _dirpath for filename in files: _fname = os.path.join(dirname, filename) if os.path.islink(_fname): yield _fname def get_file_list(self): """Iterator for a list of files in the archive, to allow clean to iterate over. Will not include symlinks, as those are handled separately """ for dirname, _, files in os.walk(self.extracted_path): for filename in files: _fname = os.path.join(dirname, filename.lstrip('/')) if not os.path.islink(_fname): yield _fname def get_directory_list(self): """Return a list of all directories within the archive""" dir_list = [] for dirname, _, _ in os.walk(self.extracted_path): dir_list.append(dirname) return dir_list def update_sub_count(self, fname, count): """Called when a file has finished being parsed and used to track total substitutions made and number of files that had changes made """ self.file_sub_list.append(fname) self.total_sub_count += count def get_file_path(self, fname): """Return the filepath of a specific file within the archive so that it may be selectively inspected if it exists """ _path = os.path.join(self.extracted_path, fname.lstrip('/')) return _path if os.path.exists(_path) else '' def should_skip_file(self, filename): """Checks the provided filename against a list of filepaths to not perform obfuscation on, as defined in self.skip_list Positional arguments: :param filename str: Filename relative to the extracted archive root """ if (not os.path.isfile(self.get_file_path(filename)) and not os.path.islink(self.get_file_path(filename))): return True for _skip in self.skip_list: if filename.startswith(_skip) or re.match(_skip, filename): return True return False def should_remove_file(self, fname): """Determine if the file should be removed or not, due to an inability to reliably obfuscate that file based on the filename. :param fname: Filename relative to the extracted archive root :type fname: ``str`` :returns: ``True`` if the file cannot be reliably obfuscated :rtype: ``bool`` """ obvious_removes = [ r'.*\.gz$', # TODO: support flat gz/xz extraction r'.*\.xz$', r'.*\.bzip2$', r'.*\.tar\..*', # TODO: support archive unpacking r'.*\.txz$', r'.*\.tgz$', r'.*\.bin$', r'.*\.journal$', r'.*\~$' ] # if the filename matches, it is obvious we can remove them without # doing the read test for _arc_reg in obvious_removes: if re.match(_arc_reg, fname): return True _full_path = self.get_file_path(fname) if os.path.isfile(_full_path): return file_is_binary(_full_path) # don't fail on dir-level symlinks return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/archives/sos.py0000664000175000017500000000546714660147624016202 0ustar arifarif# Copyright 2021 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import tarfile from sos.cleaner.archives import SoSObfuscationArchive class SoSReportArchive(SoSObfuscationArchive): """This is the class representing an sos report, or in other words the type the archive the SoS project natively generates """ type_name = 'report' description = 'sos report archive' @classmethod def check_is_type(cls, arc_path): try: return tarfile.is_tarfile(arc_path) and 'sosreport-' in arc_path except Exception: return False class SoSReportDirectory(SoSReportArchive): """This is the archive class representing a build directory, or in other words what `sos report --clean` will end up using for in-line obfuscation """ type_name = 'report_dir' description = 'sos report directory' @classmethod def check_is_type(cls, arc_path): if os.path.isdir(arc_path): return 'sos_logs' in os.listdir(arc_path) return False class SoSCollectorArchive(SoSObfuscationArchive): """Archive class representing the tarball created by ``sos collect``. It will not provide prep files on its own, however it will provide a list of SoSReportArchive's which will then be used to prep the parsers """ type_name = 'collect' description = 'sos collect tarball' is_nested = True @classmethod def check_is_type(cls, arc_path): try: return (tarfile.is_tarfile(arc_path) and 'sos-collect' in arc_path) except Exception: return False def get_nested_archives(self): self.extract(quiet=True) _path = self.extracted_path archives = [] for fname in os.listdir(_path): arc_name = os.path.join(_path, fname) if 'sosreport-' in fname and tarfile.is_tarfile(arc_name): archives.append(SoSReportArchive(arc_name, self.tmpdir)) return archives class SoSCollectorDirectory(SoSCollectorArchive): """The archive class representing the temp directory used by ``sos collect`` when ``--clean`` is used during runtime. """ type_name = 'collect_dir' description = 'sos collect directory' @classmethod def check_is_type(cls, arc_path): if os.path.isdir(arc_path): for fname in os.listdir(arc_path): if 'sos-collector-' in fname: return True return False # vim: set et ts=4 sw=4 : sos-4.8.0/sos/cleaner/archives/insights.py0000664000175000017500000000211714660147624017213 0ustar arifarif# Copyright 2021 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import tarfile from sos.cleaner.archives import SoSObfuscationArchive class InsightsArchive(SoSObfuscationArchive): """This class represents archives generated by the insights-client utility for RHEL systems. """ type_name = 'insights' description = 'insights-client archive' @classmethod def check_is_type(cls, arc_path): try: return tarfile.is_tarfile(arc_path) and 'insights-' in arc_path except Exception: return False def get_archive_root(self): top = self.archive_path.split('/')[-1].split('.tar')[0] if self.tarobj.firstmember.name == '.': top = './' + top return top sos-4.8.0/sos/cleaner/archives/generic.py0000664000175000017500000000266614660147624017010 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import tarfile from sos.cleaner.archives import SoSObfuscationArchive class DataDirArchive(SoSObfuscationArchive): """A plain directory on the filesystem that is not directly associated with any known or supported collection utility """ type_name = 'data_dir' description = 'unassociated directory' @classmethod def check_is_type(cls, arc_path): return os.path.isdir(arc_path) def set_archive_root(self): return os.path.abspath(self.archive_path) class TarballArchive(SoSObfuscationArchive): """A generic tar archive that is not associated with any known or supported collection utility """ type_name = 'tarball' description = 'unassociated tarball' @classmethod def check_is_type(cls, arc_path): try: return tarfile.is_tarfile(arc_path) except Exception: return False def set_archive_root(self): if self.tarobj.firstmember.isdir(): return self.tarobj.firstmember.name return '' sos-4.8.0/sos/cleaner/mappings/0000775000175000017500000000000014660147624015022 5ustar arifarifsos-4.8.0/sos/cleaner/mappings/keyword_map.py0000664000175000017500000000206514660147624017720 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.mappings import SoSMap class SoSKeywordMap(SoSMap): """Mapping store for user provided keywords By default, this map will perform no matching or obfuscation. It relies entirely on the use of the --keywords option by the user. Any keywords provided are then obfuscated into 'obfuscatedwordX', where X is an incrementing integer. """ match_full_words_only = True word_count = 0 def sanitize_item(self, item): _ob_item = f"obfuscatedword{self.word_count}" self.word_count += 1 if _ob_item in self.dataset.values(): return self.sanitize_item(item) return _ob_item sos-4.8.0/sos/cleaner/mappings/__init__.py0000664000175000017500000001200714660147624017133 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from threading import Lock class SoSMap(): """Standardized way to store items with their obfuscated counterparts. Each type of sanitization that SoSCleaner supports should have a corresponding SoSMap() object, to allow for easy retrieval of obfuscated items. """ # used for regex skips in parser.parse_line() ignore_matches = [] # used for filename obfuscations in parser.parse_string_for_keys() skip_keys = [] compile_regexes = True ignore_short_items = False match_full_words_only = False def __init__(self): self.dataset = {} self._regexes_made = set() self.compiled_regexes = [] self.lock = Lock() def ignore_item(self, item): """Some items need to be completely ignored, for example link-local or loopback addresses should not be obfuscated """ if not item or item in self.skip_keys or item in self.dataset.values()\ or (self.ignore_short_items and len(item) <= 3): return True for skip in self.ignore_matches: if re.match(skip, item, re.I): return True return False def add(self, item): """Add a particular item to the map, generating an obfuscated pair for it. Positional arguments: :param item: The plaintext object to obfuscate """ if self.ignore_item(item): return item with self.lock: self.dataset[item] = self.sanitize_item(item) if self.compile_regexes: self.add_regex_item(item) return self.dataset[item] def add_regex_item(self, item): """Add an item to the regexes dict and then re-sort the list that the parsers will use during parse_line() :param item: The unobfuscated item to generate a regex for :type item: ``str`` """ if self.ignore_item(item): return if item not in self._regexes_made: # save the item in a set to avoid clobbering existing regexes, # as searching this set is significantly faster than searching # through the actual compiled_regexes list, especially for very # large collections of entries self._regexes_made.add(item) # add the item, Pattern tuple directly to the compiled_regexes list # and then sort the existing list, rather than rebuild the list # from scratch every time we add something like we would do if we # tracked/saved the item and the Pattern() object in a dict or in # the set above self.compiled_regexes.append((item, self.get_regex_result(item))) self.compiled_regexes.sort(key=lambda x: len(x[0]), reverse=True) def get_regex_result(self, item): """Generate the object/value that is used by the parser when iterating over pre-generated regexes during parse_line(). For most parsers this will simply be a ``re.Pattern()`` object, but for more complex parsers this can be overridden to provide a different object, e.g. a tuple, for that parer's specific iteration needs. :param item: The unobfuscated string to generate the regex for :type item: ``str`` :returns: A compiled regex pattern for the item :rtype: ``re.Pattern`` """ if self.match_full_words_only: item = rf'(?=\b|_|-){re.escape(item)}(?=\b|_|-)' else: item = re.escape(item) return re.compile(item, re.I) def sanitize_item(self, item): """Perform the obfuscation relevant to the item being added to the map. This should be overridden by each type of map that subclasses SoSMap Positional arguments: :param item: The plaintext object to obfuscate """ return item def get(self, item): """Retrieve an item's obfuscated counterpart from the map. If the item does not yet exist in the map, add it by generating one on the fly """ if self.ignore_item(item): return item if item not in self.dataset: return self.add(item) return self.dataset[item] def conf_update(self, config): """Update the map using information from a previous run to ensure that we have consistent obfuscation between reports Positional arguments: :param config: A dict of mappings with the form of {clean_entry: 'obfuscated_entry'} """ self.dataset.update(config) sos-4.8.0/sos/cleaner/mappings/mac_map.py0000664000175000017500000000542514660147624016777 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import random import re from sos.cleaner.mappings import SoSMap class SoSMacMap(SoSMap): """Mapping store for MAC addresses MAC addresses added to this map will be broken into two halves, vendor and device like how MAC addresses are normally crafted. For the vendor hextets, obfuscation will take the form of 53:4f:53, or 'SOS' in hex. The following device hextets will be randomized, for example a MAC address of '60:55:cb:4b:c9:27' may be obfuscated into '53:4f:53:79:ac:29' or similar This map supports both 48-bit and 64-bit MAC addresses. 48-bit address may take the form of either: MM:MM:MM:SS:SS:SS MM-MM-MM-SS-SS-SS For 64-bit addresses, the identifier injected by IPv6 standards is used in obfuscated returns. These addresses may take either of these forms: MM:MM:MM:FF:FE:SS:SS:SS MMMM:MMFF:FESS:SSSS All mapped mac addresses are converted to lower case. Dash delimited styles will be converted to colon-delimited style. """ ignore_matches = [ 'ff:ff:ff:ff:ff:ff', '00:00:00:00:00:00' ] mac_template = '53:4f:53:%s:%s:%s' mac6_template = '53:4f:53:ff:fe:%s:%s:%s' mac6_quad_template = '534f:53ff:fe%s:%s%s' compile_regexes = False def add(self, item): item = item.replace('-', ':').lower().strip('=.,').strip() return super().add(item) def get(self, item): item = item.replace('-', ':').lower().strip('=.,').strip() return super().get(item) def sanitize_item(self, item): """Randomize the device hextets, and append those to our 'vendor' hextet """ hexdigits = "0123456789abdcef" hextets = [] for _ in range(0, 3): hextets.append(''.join(random.choice(hexdigits) for x in range(2))) hextets = tuple(hextets) # match 64-bit IPv6 MAC addresses matching MM:MM:MM:FF:FE:SS:SS:SS if re.match('(([0-9a-fA-F]{2}:){7}[0-9a-fA-F]{2})', item): return self.mac6_template % hextets # match 64-bit IPv6 MAC addresses matching MMMM:MMFF:FESS:SSSS if re.match('(([0-9a-fA-F]{4}:){3}([0-9a-fA-F]){4})', item): return self.mac6_quad_template % hextets # match 48-bit IPv4 MAC addresses if re.match('([0-9a-fA-F][:_]?){12}', item): return self.mac_template % hextets return None sos-4.8.0/sos/cleaner/mappings/ipv6_map.py0000664000175000017500000002655414660147624017131 0ustar arifarif# Copyright 2022 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import ipaddress from random import getrandbits from sos.cleaner.mappings import SoSMap def generate_hextets(hextets): """Generate a random set of hextets, based on the length of the source hextet. If any hextets are compressed, keep that compression. E.G. '::1234:bcd' will generate a leading empty '' hextet, followed by two 4-character hextets. :param hextets: The extracted hextets from a source address :type hextets: ``list`` :returns: A set of randomized hextets for use in an obfuscated address :rtype: ``list`` """ return [random_hex(4) if h else '' for h in hextets] def random_hex(length): """Generate a string of size length of random hex characters. :param length: The number of characters to generate :type length: ``int`` :returns: A string of ``length`` hex characters :rtype: ``str`` """ return f"{getrandbits(4*length):0{length}x}" class SoSIPv6Map(SoSMap): """Mapping for IPv6 addresses and networks. Much like the IP map handles IPv4 addresses, this map is designed to take IPv6 strings and obfuscate them consistently to maintain network topology. To do this, addresses will be manipulated by the ipaddress library. If an IPv6 address is encountered without a netmask, it is assumed to be a /64 address. """ networks = {} ignore_matches = [ r'^::1/.*', r'::/0', r'fd53:.*', r'^53..:' ] first_hexes = ['534f'] compile_regexes = False version = 1 def conf_update(self, config): """Override the base conf_update() so that we can load the existing networks into ObfuscatedIPv6Network() objects for the current run. """ if 'networks' not in config: return for network in config['networks']: _orig = ipaddress.ip_network(network) _obfuscated = config['networks'][network]['obfuscated'] _net = self._get_network(_orig, _obfuscated) self.dataset[_net.original_address] = _net.obfuscated_address for host in config['networks'][network]['hosts']: _ob_host = config['networks'][network]['hosts'][host] _net.add_obfuscated_host_address(host, _ob_host) self.dataset[host] = _ob_host def sanitize_item(self, item): _prefix = item.split('/')[-1] if '/' in item else '' _ipaddr = item if not _prefix: # assume a /64 default per protocol _ipaddr += "/64" try: _addr = ipaddress.ip_network(_ipaddr) # ipaddr was an actual network per protocol _net = self._get_network(_addr) _ipaddr = _net.obfuscated_address except ValueError: # A ValueError is raised from the ipaddress module when passing # an address such as 2620:52:0:2d80::4fe/64, which has host bits # '::4fe' set - the /64 is generally interpreted only for network # addresses. We use this behavior to properly obfuscate the network # before obfuscating a host address within that network _addr = ipaddress.ip_network(_ipaddr, strict=False) _net = self._get_network(_addr) if _net.network_addr not in self.dataset: self.dataset[_net.original_address] = _net.obfuscated_address # then, get the address within the network _hostaddr = ipaddress.ip_address(_ipaddr.split('/')[0]) _ipaddr = _net.obfuscate_host_address(_hostaddr) if _prefix and '/' not in _ipaddr: return f"{_ipaddr}/{_prefix}" return _ipaddr def _get_network(self, address, obfuscated=''): """Attempt to find an existing ObfuscatedIPv6Network object from which to either find an existing obfuscated match, or create a new one. If no such object already exists, create it. """ _addr = address.compressed if _addr not in self.networks: self.networks[_addr] = ObfuscatedIPv6Network(address, obfuscated, self.first_hexes) return self.networks[_addr] class ObfuscatedIPv6Network(): """An abstraction class that represents a network that is (to be) handled by sos. Each distinct IPv6 network that we encounter will have a representative instance of this class, from which new obfuscated subnets and host addresses will be generated. This class should be built from an ``ipaddress.IPv6Network`` object. If an obfuscation string is not passed, one will be created during init. """ def __init__(self, addr, obfuscation='', used_hexes=None): """Basic setup for the obfuscated network. Minor validation on the addr used to create the instance, as well as on an optional ``obfuscation`` which if set, will serve as the obfuscated_network address. :param addr: The *un*obfuscated network to be handled :type addr: ``ipaddress.IPv6Network`` :param obfuscation: An optional pre-determined string representation of the obfuscated network address :type obfuscation: ``str`` :param used_hexes: A list of already used hexes for the first hextet of a potential global address obfuscation :type used_hexes: ``list`` """ if not isinstance(addr, ipaddress.IPv6Network): raise Exception('Invalid network: not an IPv6Network object') self.addr = addr self.prefix = addr.prefixlen self.network_addr = addr.network_address.compressed self.hosts = {} if used_hexes is None: self.first_hexes = ['534f'] else: self.first_hexes = used_hexes if not obfuscation: self._obfuscated_network = self._obfuscate_network_address() else: if not isinstance(obfuscation, str): raise TypeError(f"Pre-determined obfuscated network address " f"must be str, not {type(obfuscation)}") self._obfuscated_network = obfuscation.split('/')[0] @property def obfuscated_address(self): return f"{self._obfuscated_network}/{self.prefix}" @property def original_address(self): return self.addr.compressed def _obfuscate_network_address(self): """Generate the obfuscated pair for the network address. This is determined based on the netmask of the network this class was built on top of. """ if self.addr.is_global: return self._obfuscate_global_address() if self.addr.is_link_local: # link-local addresses are always fe80::/64. This is not sensitive # in itself, and retaining the information that an address is a # link-local address is important for problem analysis, so don't # obfuscate this network information. return self.network_addr if self.addr.is_private: return self._obfuscate_private_address() return self.network_addr def _obfuscate_global_address(self): """Global unicast addresses have a 48-bit global routing prefix and a 16-bit subnet. We set the global routing prefix to a static sos-specific identifier that could never be seen in the wild, '534f:' We then randomize the subnet hextet. """ _hextets = self.network_addr.split(':')[1:] _ob_hex = ['534f'] if all(not c for c in _hextets): # we have only a single defined hextet, e.g. ff00::/64, so we need # to not use the standard first-hex identifier or we'll overlap # every similar address obfuscation. # Set the leading bits to 53, but increment upwards from there for # when we exceed 256 networks obfuscated in this manner. _start = 53 + (len(self.first_hexes) // 256) _ob_hex = f"{_start}{random_hex(2)}" while _ob_hex in self.first_hexes: # prevent duplicates _ob_hex = f"{_start}{random_hex(2)}" self.first_hexes.append(_ob_hex) _ob_hex = [_ob_hex] _ob_hex.extend(generate_hextets(_hextets)) return ':'.join(_ob_hex) def _obfuscate_private_address(self): """The first 8 bits will always be 'fd', the next 40 bits are meant to be a global ID, followed by 16 bits for the subnet. To keep things relatively simply we maintain the first hextet as 'fd53', and then randomize any remaining hextets """ _hextets = self.network_addr.split(':')[1:] _ob_hex = ['fd53'] _ob_hex.extend(generate_hextets(_hextets)) return ':'.join(_ob_hex) def obfuscate_host_address(self, addr): """Given an unobfuscated address, generate an obfuscated match for it, and save it to this network for tracking during the execution of clean. Note: another way to do this would be to convert the obfuscated network to bytes, and add a random amount to that based on the number of addresses that the network can support and from that new bytes count craft a new IPv6 address. This has the advantage of absolutely guaranteeing the new address is within the network space (whereas the method employed below could *theoretically* generate an overlapping address), but would in turn remove any ability to compress obfuscated addresses to match the general format/syntax of the address it is replacing. For the moment, it is assumed that being able to maintain a quick mental note of "unobfuscated device ff00::1 is obfuscated device 53ad::a1b2" is more desireable than "ff00::1 is now obfuscated as 53ad::1234:abcd:9876:a1b2:". :param addr: The unobfuscated IPv6 address :type addr: ``ipaddress.IPv6Address`` :returns: An obfuscated address within this network :rtype: ``str`` """ def _generate_address(host): return ''.join([ self._obfuscated_network, ':'.join(generate_hextets(host.split(':'))) ]) if addr.compressed not in self.hosts: # separate host from the address by removing its network prefix _n = self.network_addr.rstrip(':') _host = addr.compressed[len(_n):].lstrip(':') _ob_host = _generate_address(_host) while _ob_host in self.hosts.values(): _ob_host = _generate_address(_host) self.add_obfuscated_host_address(addr.compressed, _ob_host) return self.hosts[addr.compressed] def add_obfuscated_host_address(self, host, obfuscated): """Adds an obfuscated pair to the class for tracking and ongoing consistency in obfuscation. """ self.hosts[host] = obfuscated sos-4.8.0/sos/cleaner/mappings/ip_map.py0000664000175000017500000001717514660147624016654 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import ipaddress import random from sos.cleaner.mappings import SoSMap class SoSIPMap(SoSMap): """A mapping store for IP addresses Each IP address added to this map is chcked for subnet membership. If that subnet already exists in the map, then IP addresses are deterministically generated sequentially within that subnet. For example, if a given IP is matched to subnet 192.168.1.0/24 then 192.168.1 may be obfuscated to 100.11.12.0/24. Each IP address in the original 192.168.1.0/24 subnet will then be assigned an address in 100.11.12.0/24 sequentially, such as 100.11.12.1, 100.11.12.2, etc... Internally, the ipaddress library is used to manipulate the address objects however, when retrieved by SoSCleaner any values will be strings. """ ignore_matches = [ r'127.*', r'::1', r'0\.(.*)?', r'1\.(.*)?', r'8.8.8.8', r'8.8.4.4', r'169.254.*', r'255.*' ] _networks = {} network_first_octet = 100 skip_network_octets = ['127', '169', '172', '192'] compile_regexes = False def ip_in_dataset(self, ipaddr): """There are multiple ways in which an ip address could be handed to us in a way where we're matching against a previously obfuscated address. Here, match the ip address to any of the obfuscated addresses we've already created """ for _ip in self.dataset.values(): if str(ipaddr).split('/', maxsplit=1)[0] == _ip.split('/')[0]: return True return False def get(self, item): """Ensure that when requesting an obfuscated address, we return a str object instead of an IPv(4|6)Address object """ filt_start = ('/', '=', ']', ')') if item.startswith(filt_start): item = item.lstrip(''.join(filt_start)) if item in self.dataset.keys(): return self.dataset[item] if self.ignore_item(item) or self.ip_in_dataset(item): return item # it's not in there, but let's make sure we haven't previously added # an address with a CIDR notation and we're now looking for it without # that notation if '/' not in item: for key, value in self.dataset.items(): if key.startswith(item): return value.split('/')[0] # fallback to the default map behavior of adding it fresh return self.add(item) def set_ip_cidr_from_existing_subnet(self, addr): """Determine if a given address is in a subnet of an already obfuscated network and if it is, then set the address' network to the network object we're tracking. This allows us to match ip addresses with or without a CIDR notation and maintain proper network relationships. """ nets = [] for net in self._networks: if addr.ip == net.broadcast_address: addr.network = net return if addr.ip in net: nets.append(net) # assign the address to the smallest network that was matched. This is # necessary due to certain files specifying addresses that cause the # ipaddress library to create artificially huge subnets that will # include the actual subnets used by the system if nets: nets.sort(key=lambda n: n.prefixlen, reverse=True) addr.network = nets[0] def sanitize_item(self, item): """Given an IP address, sanitize it to an obfuscated network or host address as appropriate """ try: addr = ipaddress.ip_interface(item) except ValueError: # not an IP, add it to the skip list to avoid flooding logs self.ignore_matches.append(item) raise network = addr.network if str(network.netmask) == '255.255.255.255': # check to see if this IP is in a subnet of an already obfuscated # network and if it has, replace the default /32 netmask that # ipaddress applies to no CIDR-notated addresses self.set_ip_cidr_from_existing_subnet(addr) else: # we have a CIDR notation, so generate an obfuscated network # address and then generate an IP address within that network's # range self.sanitize_network(network) return self.sanitize_ipaddr(addr) def sanitize_network(self, network): """Obfuscate the network address provided, and if there are host bits in the address then obfuscate those as well """ # check if the address is in a network we've already encountered if network not in self._networks: self._new_obfuscated_network(network) def sanitize_ipaddr(self, addr): """Obfuscate the IP address within the known obfuscated network """ # get the obfuscated network object if addr.network in self._networks: _obf_network = self._networks[addr.network] # if the plain address is the broadcast address for it's own # network, then assign the broadcast address for the obfuscated # network if addr.ip == addr.network.broadcast_address: return str(_obf_network.broadcast_address) # otherwise within that obfuscated network grab the next available # address from it for _ip in _obf_network.hosts(): if not self.ip_in_dataset(_ip): # the ipaddress module does not assign the network's # netmask to hosts in the hosts() generator for some reason return f"{str(_ip)}/{_obf_network.prefixlen}" # ip is a single ip address without the netmask return self._new_obfuscated_single_address() def _new_obfuscated_single_address(self): def _gen_address(): _octets = [] for _ in range(0, 4): _octets.append(random.randint(11, 99)) return f"{_octets[0]}.{_octets[1]}.{_octets[2]}.{_octets[3]}" _addr = _gen_address() if _addr in self.dataset.values(): return self._new_obfuscated_single_address() return _addr def _new_obfuscated_network(self, network): """Generate an obfuscated network address for the network address given which will allow us to maintain network relationships without divulging actual network details Positional arguments: :param network: An ipaddress.IPv{4|6)Network object """ _obf_network = None if isinstance(network, ipaddress.IPv4Network): if self.network_first_octet in self.skip_network_octets: self.network_first_octet += 1 _obf_address = f"{self.network_first_octet}.0.0.0" _obf_mask = network.with_netmask.split('/')[1] _obf_network = ipaddress.IPv4Network(f"{_obf_address}/{_obf_mask}") self.network_first_octet += 1 if isinstance(network, ipaddress.IPv6Network): # TODO: define this pass if _obf_network: self._networks[network] = _obf_network self.dataset[str(network)] = str(_obf_network) sos-4.8.0/sos/cleaner/mappings/hostname_map.py0000664000175000017500000002401514660147624020051 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.cleaner.mappings import SoSMap class SoSHostnameMap(SoSMap): """Mapping store for hostnames and domain names Hostnames are obfuscated using an incrementing counter based on the total number of hosts matched regardless of domain name. Domain names are obfuscated based on the host's hostname, plus any user defined domains passed in by the `--domains` options. Domains are obfuscated as whole units, meaning the domains 'example.com' and 'host.foo.example.com' will be separately obfuscated with no relation for example as 'obfuscatedomdain1.com' and 'obfuscatedomain2.com'. Top-level domains are left untouched. """ ignore_matches = [ 'localhost', '.*localdomain.*', '^com..*' ] skip_keys = [ 'www', 'api' ] strip_exts = ('.yaml', '.yml', '.crt', '.key', '.pem', '.log', '.repo', '.rules', '.conf', '.cfg') ignore_short_items = True match_full_words_only = True host_count = 0 domain_count = 0 _domains = {} hosts = {} def load_domains_from_map(self): """Because we use 'intermediary' dicts for host names and domain names in this parser, we need to re-inject entries from the map_file into these dicts and not just the underlying 'dataset' dict """ for domain, ob_pair in self.dataset.items(): if len(domain.split('.')) == 1: self.hosts[domain.split('.')[0]] = self.dataset[domain] else: if ob_pair.startswith('obfuscateddomain'): # directly exact domain matches self._domains[domain] = ob_pair.split('.')[0] continue # strip the host name and trailing top-level domain so that # we in inject the domain properly for later string matching # note: this is artificially complex due to our stance on # preserving TLDs. If in the future the project decides to # obfuscate TLDs as well somehow, then this will all become # much simpler _domain_to_inject = '.'.join(domain.split('.')[1:-1]) if not _domain_to_inject: continue for existing_domain, value in self.dataset.items(): _existing = '.'.join(existing_domain.split('.')[:-1]) if _existing == _domain_to_inject: _ob_domain = '.'.join(value.split('.')[:-1]) self._domains[_domain_to_inject] = _ob_domain self.set_initial_counts() def get_regex_result(self, item): """Override the base get_regex_result() to provide a regex that, if this is an FQDN or a straight domain, will include an underscore formatted regex as well. """ if '.' in item: item = item.replace('.', '(\\.|_)') return re.compile(item, re.I) def set_initial_counts(self): """Set the initial counter for host and domain obfuscation numbers based on what is already present in the mapping. """ # hostnames/short names try: h = sorted(self.hosts.values(), reverse=True)[0].split('host')[1] self.host_count = int(h) + 1 except IndexError: # no hosts loaded yet pass # domain names try: d = sorted(self._domains.values(), reverse=True)[0].split('domain') self.domain_count = int(d[1].split('.')[0]) + 1 except IndexError: # no domains loaded yet pass def domain_name_in_loaded_domains(self, domain): """Check if a potential domain is in one of the domains we've loaded and should be obfuscated """ if domain in self._domains: return True host = domain.split('.') no_tld = '.'.join(domain.split('.')[0:-1]) if len(host) == 1: # don't block on host's shortname return host[0] in self.hosts if any(no_tld.endswith(_d) for _d in self._domains): return True return False def get(self, item): # pylint: disable=too-many-branches prefix = '' suffix = '' final = None # The regex pattern match may include a leading and/or trailing '_' # character due to the need to use word boundary matching, so we need # to strip these from the string during processing, but still keep them # in the returned string to not mangle the string replacement in the # context of the file or filename while item.startswith(('.', '_')): prefix += item[0] item = item[1:] while item.endswith(('.', '_')): suffix += item[-1] item = item[0:-1] if item in self.dataset: return self.dataset[item] if not self.domain_name_in_loaded_domains(item.lower()): # no match => return the original string with optional # leading/trailing '.' or '_' characters return ''.join([prefix, item, suffix]) if item.endswith(self.strip_exts): ext = '.' + item.split('.')[-1] item = item.replace(ext, '') suffix += ext if item not in self.dataset.keys(): # try to account for use of '-' in names that include hostnames # and don't create new mappings for each of these for _existing in sorted(self.dataset.keys(), reverse=True, key=len): _host_substr = False _test = item.split(_existing) _h = _existing.split('.') # avoid considering a full FQDN match as a new match off of # the hostname of an existing match if _h[0] and _h[0] in self.hosts.keys(): _host_substr = True if len(_test) == 1 or not _test[0]: # does not match existing obfuscation continue if not _host_substr and (_test[0].endswith('.') or item.endswith(_existing)): # new hostname in known domain final = super().get(item) break if item.split(_test[0]): # string that includes existing FQDN obfuscation substring # so, only obfuscate the FQDN part try: itm = item.split(_test[0])[1] final = _test[0] + super().get(itm) break except Exception: # fallback to still obfuscating the entire item pass if not final: final = super().get(item) return prefix + final + suffix def sanitize_item(self, item): host = item.split('.') if len(host) == 1: # we have a shortname for a host return self.sanitize_short_name(host[0].lower()) if len(host) == 2: # we have just a domain name, e.g. example.com dname = self.sanitize_domain(host) if all(h.isupper() for h in host): dname = dname.upper() return dname if len(host) > 2: # we have an FQDN, e.g. foo.example.com hostname = host[0] domain = host[1:] # obfuscate the short name if len(hostname) > 2: ob_hostname = self.sanitize_short_name(hostname.lower()) else: # by best practice it appears the host part of the fqdn was cut # off due to some form of truncating, as such don't obfuscate # short strings that are likely to throw off obfuscation of # unrelated bits and paths ob_hostname = 'unknown' ob_domain = self.sanitize_domain(domain) self.dataset[item] = ob_domain _fqdn = '.'.join([ob_hostname, ob_domain]) if all(h.isupper() for h in host): _fqdn = _fqdn.upper() return _fqdn return None def sanitize_short_name(self, hostname): """Obfuscate the short name of the host with an incremented counter based on the total number of obfuscated host names """ if not hostname or hostname in self.skip_keys: return hostname if hostname not in self.dataset: ob_host = f"host{self.host_count}" self.hosts[hostname] = ob_host self.host_count += 1 self.dataset[hostname] = ob_host self.add_regex_item(hostname) return self.dataset[hostname] def sanitize_domain(self, domain): """Obfuscate the domainname, broken out into subdomains. Top-level domains are ignored. """ for _skip in self.ignore_matches: # don't obfuscate vendor domains if re.match(_skip, '.'.join(domain)): return '.'.join(domain) top_domain = domain[-1].lower() dname = '.'.join(domain[0:-1]).lower() ob_domain = self._new_obfuscated_domain(dname) ob_domain = '.'.join([ob_domain, top_domain]) self.dataset['.'.join(domain)] = ob_domain return ob_domain def _new_obfuscated_domain(self, dname): """Generate an obfuscated domain for each subdomain name given """ if dname not in self._domains: self._domains[dname] = f"obfuscateddomain{self.domain_count}" self.domain_count += 1 return self._domains[dname] sos-4.8.0/sos/cleaner/mappings/username_map.py0000664000175000017500000000221514660147624020050 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.mappings import SoSMap class SoSUsernameMap(SoSMap): """Mapping to store usernames matched from ``lastlog`` output. Usernames are obfuscated as ``obfuscateduserX`` where ``X`` is a counter that gets incremented for every new username found. Note that this specifically obfuscates user_names_ and not UIDs. """ ignore_short_items = True match_full_words_only = True name_count = 0 def sanitize_item(self, item): """Obfuscate a new username not currently found in the map """ ob_name = f"obfuscateduser{self.name_count}" self.name_count += 1 if ob_name in self.dataset.values(): return self.sanitize_item(item.lower()) return ob_name sos-4.8.0/sos/cleaner/parsers/0000775000175000017500000000000014660147624014663 5ustar arifarifsos-4.8.0/sos/cleaner/parsers/__init__.py0000664000175000017500000001516114660147624017000 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. # pylint: disable=no-member import re class SoSCleanerParser(): """Parsers are used to build objects that will take a line as input, parse it for a particular pattern (E.G. IP addresses) and then make any necessary subtitutions by referencing the SoSMap() associated with the parser. Ideally a new parser subclass will only need to set the class level attrs in order to be fully functional. :param conf_file: The configuration file to read from :type conf_file: ``str`` :cvar name: The parser name, used in logging errors :vartype name: ``str`` :cvar regex_patterns: A list of regex patterns to iterate over for every line processed :vartype regex_patterns: ``list`` :cvar mapping: Used by the parser to store and obfuscate matches :vartype mapping: ``SoSMap()`` :cvar map_file_key: The key in the ``map_file`` to read when loading previous obfuscation matches :vartype map_file_key: ``str`` """ name = 'Undefined Parser' regex_patterns = [] skip_line_patterns = [] parser_skip_files = [] # list of skip files relevant to a parser skip_cleaning_files = [] # list of global skip files from cmdline args map_file_key = 'unset' compile_regexes = True def __init__(self, config={}, skip_cleaning_files=[]): if self.map_file_key in config: self.mapping.conf_update(config[self.map_file_key]) self.skip_cleaning_files = skip_cleaning_files self._generate_skip_regexes() def _generate_skip_regexes(self): """Generate the regexes for the parser's configured parser_skip_files or global skip_cleaning_files, so that we don't regenerate them on every file being examined for if the parser should skip a given file. """ self.skip_patterns = [] for p in self.parser_skip_files + self.skip_cleaning_files: self.skip_patterns.append(re.compile(p)) def generate_item_regexes(self): """Generate regexes for items the parser will be searching for repeatedly without needing to generate them for every file and/or line we process Not used by all parsers. """ if not self.compile_regexes: return for obitem in self.mapping.dataset: self.mapping.add_regex_item(obitem) def parse_line(self, line): """This will be called for every line in every file we process, so that every parser has a chance to scrub everything. This will first try to identify needed obfuscations for items we have already encountered (if the parser uses compiled regexes that is) and make those substitutions early on. After which, we will then parse the line again looking for new matches. """ count = 0 for skip_pattern in self.skip_line_patterns: if re.match(skip_pattern, line, re.I): return line, count if self.compile_regexes: line, _rcount = self._parse_line_with_compiled_regexes(line) count += _rcount line, _count = self._parse_line(line) count += _count return line, count def _parse_line_with_compiled_regexes(self, line): """Check the provided line against known items we have encountered before and have pre-generated regex Pattern() objects for. :param line: The line to parse for possible matches for obfuscation :type line: ``str`` :returns: The obfuscated line and the number of changes made :rtype: ``str``, ``int`` """ count = 0 for item, reg in self.mapping.compiled_regexes: if reg.search(line): line, _count = reg.subn(self.mapping.get(item.lower()), line) count += _count return line, count def _parse_line(self, line): """Check the provided line against the parser regex patterns to try and discover _new_ items to obfuscate :param line: The line to parse for possible matches for obfuscation :type line: ``str`` :returns: The obfsucated line, and the number of changes made :rtype: ``tuple``, ``(str, int))`` """ count = 0 for pattern in self.regex_patterns: matches = [m[0] for m in re.findall(pattern, line, re.I)] if matches: matches.sort(reverse=True, key=len) count += len(matches) for match in matches: match = match.strip() if match in self.mapping.dataset.values(): continue new_match = self.mapping.get(match) if new_match != match: line = line.replace(match, new_match) return line, count def parse_string_for_keys(self, string_data): """Parse a given string for instances of any obfuscated items, without applying the normal regex comparisons first. This is mainly used to obfuscate filenames that have, for example, hostnames in them. Rather than try to regex match the string_data, just use the builtin checks for substrings matching known obfuscated keys :param string_data: The line to be parsed :type string_data: ``str`` :returns: The obfuscated line :rtype: ``str`` """ if self.compile_regexes: for item, reg in self.mapping.compiled_regexes: if reg.search(string_data): string_data = reg.sub(self.mapping.get(item.lower()), string_data) else: for k, ob in sorted(self.mapping.dataset.items(), reverse=True, key=lambda x: len(x[0])): if k in self.mapping.skip_keys: continue if k in string_data: string_data = string_data.replace(k, ob) return string_data def get_map_contents(self): """Get the contents of the mapping used by the parser :returns: All matches and their obfuscate counterparts :rtype: ``dict`` """ return self.mapping.dataset sos-4.8.0/sos/cleaner/parsers/username_parser.py0000664000175000017500000000244514660147624020435 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.parsers import SoSCleanerParser from sos.cleaner.mappings.username_map import SoSUsernameMap class SoSUsernameParser(SoSCleanerParser): """Parser for obfuscating usernames within an sos report archive. Note that this parser does not rely on regex matching directly, like most other parsers do. Instead, usernames are discovered via scraping the collected output of lastlog. As such, we do not discover new usernames later on, and only usernames present in lastlog output will be obfuscated, and those passed via the --usernames option if one is provided. """ name = 'Username Parser' map_file_key = 'username_map' regex_patterns = [] def __init__(self, config, skip_cleaning_files=[]): self.mapping = SoSUsernameMap() super().__init__(config, skip_cleaning_files) def _parse_line(self, line): return line, 0 sos-4.8.0/sos/cleaner/parsers/keyword_parser.py0000664000175000017500000000157614660147624020306 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.parsers import SoSCleanerParser from sos.cleaner.mappings.keyword_map import SoSKeywordMap class SoSKeywordParser(SoSCleanerParser): """Handles parsing for user provided keywords """ name = 'Keyword Parser' map_file_key = 'keyword_map' def __init__(self, config, skip_cleaning_files=[]): self.mapping = SoSKeywordMap() super().__init__(config, skip_cleaning_files) def _parse_line(self, line): return line, 0 sos-4.8.0/sos/cleaner/parsers/ipv6_parser.py0000664000175000017500000000443514660147624017503 0ustar arifarif# Copyright 2022 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.parsers import SoSCleanerParser from sos.cleaner.mappings.ipv6_map import SoSIPv6Map class SoSIPv6Parser(SoSCleanerParser): """Parser for handling IPv6 networks and addresses""" name = 'IPv6 Parser' map_file_key = 'ipv6_map' regex_patterns = [ # Attention: note that this is a single long regex, not several entries # This is initially based off of two regexes from the Java library # for validating an IPv6 string. However, this is modified to begin and # end with a negative lookbehind to ensure that a substring of 'ed::' # is not extracted from a log message such as 'SomeFuncUsed::ADiffFunc' # that come components may log with. Further, we optionally try to grab # a trailing prefix for the network bits. r"(? # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. from sos.cleaner.parsers import SoSCleanerParser from sos.cleaner.mappings.ip_map import SoSIPMap class SoSIPParser(SoSCleanerParser): """Handles parsing for IP addresses""" name = 'IP Parser' regex_patterns = [ # IPv4 with or without CIDR r'((? # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.cleaner.parsers import SoSCleanerParser from sos.cleaner.mappings.hostname_map import SoSHostnameMap class SoSHostnameParser(SoSCleanerParser): name = 'Hostname Parser' map_file_key = 'hostname_map' regex_patterns = [ r'(((\b|_)[a-zA-Z0-9-\.]{1,200}\.[a-zA-Z]{1,63}(\b|_)))' ] def __init__(self, config, skip_cleaning_files=[]): self.mapping = SoSHostnameMap() super().__init__(config, skip_cleaning_files) def parse_line(self, line): """This will be called for every line in every file we process, so that every parser has a chance to scrub everything. We are overriding parent method since we need to swap ordering of _parse_line_with_compiled_regexes and _parse_line calls. """ count = 0 for skip_pattern in self.skip_line_patterns: if re.match(skip_pattern, line, re.I): return line, count line, _count = self._parse_line(line) count += _count if self.compile_regexes: line, _rcount = self._parse_line_with_compiled_regexes(line) count += _rcount return line, count sos-4.8.0/sos/cleaner/parsers/mac_parser.py0000664000175000017500000000515614660147624017360 0ustar arifarif# Copyright 2020 Red Hat, Inc. Jake Hunsaker # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import re from sos.cleaner.parsers import SoSCleanerParser from sos.cleaner.mappings.mac_map import SoSMacMap # aa:bb:cc:fe:ff:dd:ee:ff IPV6_REG_8HEX = ( r'((?=0.4.20 which implements detect_from_filename method magic_mod = False try: import magic magic.detect_from_filename(__file__) magic_mod = True except (ImportError, AttributeError): from textwrap import fill msg = """\ WARNING: Failed to load 'magic' module version >= 0.4.20 which sos aims to \ use for detecting binary files. A less effective method will be used. It is \ recommended to install proper python3-magic package with the module. """ log.warning('\n' + fill(msg, 72, replace_whitespace=False) + '\n') TIMEOUT_DEFAULT = 300 __all__ = [ 'TIMEOUT_DEFAULT', 'ImporterHelper', 'SoSTimeoutError', 'TempFileUtil', 'bold', 'file_is_binary', 'fileobj', 'find', 'get_human_readable', 'grep', 'import_module', 'is_executable', 'listdir', 'parse_version', 'path_exists', 'path_isdir', 'path_isfile', 'path_islink', 'path_join', 'recursive_dict_values_by_key', 'shell_out', 'sos_get_command_output', 'tail', ] def format_version_to_pep440(ver): """ Convert the version into a PEP440 compliant version scheme.""" public_version_re = re.compile( r"^([0-9][0-9.]*(?:(?:a|b|rc|.post|.dev)[0-9]+)*)\+?" ) try: _, public, local = public_version_re.split(ver, maxsplit=1) if not local: return ver sanitized_local = re.sub("[+~]+", ".", local).strip("-") pep440_version = f"{public}+{sanitized_local}" return pep440_version except Exception as err: log.debug(f"Unable to format {ver} to pep440 format: {err}") return ver def sos_parse_version(ver, pep440=True): """ Converts the version to PEP440 format before parsing """ if pep440: ver_pep440 = format_version_to_pep440(ver) return parse_version(ver_pep440) return parse_version(ver) def tail(filename, number_of_bytes): """Returns the last number_of_bytes of filename""" with open(filename, "rb") as f: if os.stat(filename).st_size > number_of_bytes: f.seek(-number_of_bytes, 2) return f.read() def fileobj(path_or_file, mode='r'): """Returns a file-like object that can be used as a context manager""" if isinstance(path_or_file, str): try: return open(path_or_file, mode, encoding='utf-8') except IOError: log.debug(f"fileobj: {path_or_file} could not be opened") return closing(io.StringIO()) else: return closing(path_or_file) def convert_bytes(num_bytes): """Converts a number of bytes to a shorter, more human friendly format""" sizes = {'T': 1 << 40, 'G': 1 << 30, 'M': 1 << 20, 'K': 1 << 10} for symbol, size in sizes.items(): if num_bytes >= size: return f"{float(num_bytes) / size:.1f}{symbol}" return f"{num_bytes}" def file_is_binary(fname): """Helper to determine if a given file contains binary content or not. This is especially helpful for `sos clean`, which cannot obfuscate binary data and instead, by default, will remove binary files. :param fname: The full path of the file to check binaryness of :type fname: ``str`` :returns: True if binary, else False :rtype: ``bool`` """ if magic_mod: try: _ftup = magic.detect_from_filename(fname) _mimes = ['text/', 'inode/'] return ( _ftup.encoding == 'binary' and not any(_ftup.mime_type.startswith(_mt) for _mt in _mimes) ) except Exception: pass # if for some reason the above check fails or magic>=0.4.20 is not present, # fail over to checking the very first byte of the file content with open(fname, 'tr', encoding='utf-8') as tfile: try: # when opened as above (tr), reading binary content will raise # an exception tfile.read(1) return False except UnicodeDecodeError: return True def find(file_pattern, top_dir, max_depth=None, path_pattern=None): """Generator function to find files recursively. Usage:: for filename in find("*.properties", "/var/log/foobar"): print filename """ if max_depth: base_depth = os.path.dirname(top_dir).count(os.path.sep) max_depth += base_depth for path, dirlist, filelist in os.walk(top_dir): if max_depth and path.count(os.path.sep) >= max_depth: del dirlist[:] if path_pattern and not fnmatch.fnmatch(path, path_pattern): continue for name in fnmatch.filter(filelist, file_pattern): yield os.path.join(path, name) def grep(pattern, *files_or_paths): """Returns lines matched in fnames, where fnames can either be pathnames to files to grep through or open file objects to grep through line by line""" matches = [] for fop in files_or_paths: with fileobj(fop) as fo: matches.extend((line for line in fo if re.match(pattern, line))) return matches def is_executable(command, sysroot=None): """Returns if a command matches an executable on the PATH""" paths = os.environ.get("PATH", "").split(os.path.pathsep) candidates = [command] + [os.path.join(p, command) for p in paths] if sysroot: candidates += [ os.path.join(sysroot, c.lstrip('/')) for c in candidates ] return any(os.access(path, os.X_OK) for path in candidates) def sos_get_command_output(command, timeout=TIMEOUT_DEFAULT, stderr=False, chroot=None, chdir=None, env=None, foreground=False, binary=False, sizelimit=None, poller=None, to_file=False, runas=None): # pylint: disable=too-many-locals,too-many-branches """Execute a command and return a dictionary of status and output, optionally changing root or current working directory before executing command. """ # Change root or cwd for child only. Exceptions in the prexec_fn # closure are caught in the parent (chroot and chdir are bound from # the enclosing scope). def _child_prep_fn(): if chroot and chroot != '/': os.chroot(chroot) if runas: os.setgid(pwd.getpwnam(runas).pw_gid) os.setuid(pwd.getpwnam(runas).pw_uid) os.chdir(pwd.getpwnam(runas).pw_dir) if chdir: os.chdir(chdir) def _check_poller(proc): if poller() or proc.poll() == 124: proc.terminate() raise SoSTimeoutError time.sleep(0.01) if runas: pwd_user = pwd.getpwnam(runas) env.update({ 'HOME': pwd_user.pw_dir, 'LOGNAME': runas, 'PWD': pwd_user.pw_dir, 'USER': runas }) cmd_env = os.environ.copy() # ensure consistent locale for collected command output cmd_env['LC_ALL'] = 'C.UTF-8' # optionally add an environment change for the command if env: for key, value in env.items(): if value: cmd_env[key] = value else: cmd_env.pop(key, None) # use /usr/bin/timeout to implement a timeout if timeout and is_executable("timeout"): command = (f"timeout {'--foreground' if foreground else ''} {timeout}s" f" {command}") args = shlex.split(command) # Expand arguments that are wildcard root paths. expanded_args = [] for arg in args: if arg.startswith("/") and "*" in arg: expanded_arg = glob.glob(arg) if expanded_arg: expanded_args.extend(expanded_arg) else: expanded_args.append(arg) else: expanded_args.append(arg) if to_file: # pylint: disable=consider-using-with _output = open(to_file, 'w', encoding='utf-8') else: _output = PIPE try: with Popen(expanded_args, shell=False, stdout=_output, stderr=STDOUT if stderr else PIPE, bufsize=-1, env=cmd_env, close_fds=True, preexec_fn=_child_prep_fn) as p: if not to_file: reader = AsyncReader(p.stdout, sizelimit, binary) else: reader = FakeReader(p, binary) if poller: while reader.running: _check_poller(p) else: try: # override timeout=0 to timeout=None, as Popen will treat # the former as a literal 0-second timeout p.wait(timeout if timeout else None) except Exception: p.terminate() if to_file: _output.close() # until we separate timeouts from the `timeout` command # handle per-cmd timeouts via Plugin status checks reader.running = False return {'status': 124, 'output': reader.get_contents(), 'truncated': reader.is_full} if to_file: _output.close() # wait for Popen to set the returncode while p.poll() is None: pass if p.returncode in (126, 127): stdout = b"" else: stdout = reader.get_contents() return { 'status': p.returncode, 'output': stdout, 'truncated': reader.is_full } except OSError as e: if to_file: _output.close() if e.errno == errno.ENOENT: return {'status': 127, 'output': "", 'truncated': ''} raise e def import_module(module_fqname, superclasses=None): """Imports the module module_fqname and returns a list of defined classes from that module. If superclasses is defined then the classes returned will be subclasses of the specified superclass or superclasses. If superclasses is plural it must be a tuple of classes.""" module_name = module_fqname.rpartition(".")[-1] try: module = __import__(module_fqname, globals(), locals(), [module_name]) except ImportError as e: print(f'Error while trying to load module {module_fqname}: ' f' {e.__class__.__name__}') raise e modules = [class_ for cname, class_ in inspect.getmembers(module, inspect.isclass) if class_.__module__ == module_fqname] if superclasses: modules = [m for m in modules if issubclass(m, superclasses)] return modules def shell_out(cmd, timeout=30, chroot=None, runat=None): """Shell out to an external command and return the output or the empty string in case of error. """ return sos_get_command_output(cmd, timeout=timeout, chroot=chroot, chdir=runat)['output'] def get_human_readable(size, precision=2): # Credit to Pavan Gupta https://stackoverflow.com/questions/5194057/ suffixes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] suffixindex = 0 while size > 1024 and suffixindex < 4: suffixindex += 1 size = size/1024.0 return f"{size:.{precision}f}{suffixes[suffixindex]}" def _os_wrapper(path, sysroot, method, module=os.path): if sysroot and sysroot != os.sep: if not path.startswith(sysroot): path = os.path.join(sysroot, path.lstrip('/')) _meth = getattr(module, method) return _meth(path) def path_exists(path, sysroot): if '*' in path: return _os_wrapper(path, sysroot, 'glob', module=glob) return _os_wrapper(path, sysroot, 'exists') def path_isdir(path, sysroot): return _os_wrapper(path, sysroot, 'isdir') def path_isfile(path, sysroot): return _os_wrapper(path, sysroot, 'isfile') def path_islink(path, sysroot): return _os_wrapper(path, sysroot, 'islink') def listdir(path, sysroot): return _os_wrapper(path, sysroot, 'listdir', os) def path_join(path, *p, sysroot=os.sep): if sysroot and not path.startswith(sysroot): path = os.path.join(sysroot, path.lstrip(os.sep)) return os.path.join(path, *p) def bold(text): """Helper to make text bold in console output, without pulling in dependencies to the project unneccessarily. :param text: The text to make bold :type text: ``str`` :returns: The text wrapped in the ASCII codes to display as bold :rtype: ``str`` """ return '\033[1m' + text + '\033[0m' def recursive_dict_values_by_key(dobj, keys=[]): """Recursively compile all elements of a potentially nested dict by a set of keys. If a given key is a dict within ``dobj``, then _all_ elements within that dict, regardless of child keys, will be returned. For example, if a Plugin searches the devices dict for the 'storage' key, then all storage devices under the that dict (e.g. block, fibre, etc...) will be returned. However, if the Plugin specifies 'block' via ``keys``, then only the block devices within the devices['storage'] dict will be returned. Any elements passed here that are _not_ keys within the dict or any nested dicts will also be returned. :param dobj: The 'top-level' dict to intially search by :type dobj: ``dict`` :param keys: Which keys to compile elements from within ``dobj``. If no keys are given, all nested elements are returned :param keys: ``list`` of ``str`` :returns: All elements within the dict and any nested dicts :rtype: ``list`` """ _items = [] _filt = [] _items.extend(keys) if isinstance(dobj, dict): for k, v in dobj.items(): _filt.append(k) # get everything below this key, including nested dicts if not keys or k in keys: _items.extend(recursive_dict_values_by_key(v)) # recurse into this dict only for dict keys that match what # we're looking for elif isinstance(v, dict): try: # this will return a nested list, extract it _items.extend( recursive_dict_values_by_key( v[key] for key in keys if key in v )[0] ) except IndexError: # none of the keys given exist in the nested dict pass _filt.extend(v.keys()) else: _items.extend(dobj) return [d for d in _items if d not in _filt] class FakeReader(): """Used as a replacement AsyncReader for when we are writing directly to disk, and allows us to keep more simplified flows for executing, monitoring, and collecting command output. """ def __init__(self, process, binary): self.process = process self.binary = binary @property def is_full(self): return False def get_contents(self): return '' if not self.binary else b'' @property def running(self): return self.process.poll() is None class AsyncReader(threading.Thread): """Used to limit command output to a given size without deadlocking sos. Takes a sizelimit value in MB, and will compile stdout from Popen into a string that is limited to the given sizelimit. """ def __init__(self, channel, sizelimit, binary): super().__init__() self.chan = channel self.binary = binary self.chunksize = 2048 self.slots = None if sizelimit: sizelimit = sizelimit * 1048576 # convert to bytes self.slots = int(sizelimit / self.chunksize) self.deque = deque(maxlen=self.slots) self.running = True self.start() def run(self): """Reads from the channel (pipe) that is the output pipe for a called Popen. As we are reading from the pipe, the output is added to a deque. After the size of the deque exceeds the sizelimit earlier (older) entries are removed. This means the returned output is chunksize-sensitive, but is not really byte-sensitive. """ try: while True: line = self.chan.read(self.chunksize) if not line: # Pipe can remain open after output has completed break self.deque.append(line) except (ValueError, IOError): # pipe has closed, meaning command output is done pass self.running = False def get_contents(self): """Returns the contents of the deque as a string""" # block until command completes or timesout (separate from the plugin # hitting a timeout) while self.running: time.sleep(0.01) if not self.binary: return ''.join(ln.decode('utf-8', 'ignore') for ln in self.deque) return b''.join(ln for ln in self.deque) @property def is_full(self): """Checks if the deque is full, implying that output was truncated""" if not self.slots: return False return len(self.deque) == self.slots class ImporterHelper: """Provides a list of modules that can be imported in a package. Importable modules are located along the module __path__ list and modules are files that end in .py. """ def __init__(self, package): """package is a package module import my.package.module helper = ImporterHelper(my.package.module)""" self.package = package def _plugin_name(self, path): "Returns the plugin module name given the path" base = os.path.basename(path) name, _ = os.path.splitext(base) return name def _get_plugins_from_list(self, list_): plugins = [self._plugin_name(plugin) for plugin in list_ if "__init__" not in plugin and plugin.endswith(".py")] plugins.sort() return plugins def _find_plugins_in_dir(self, path): if os.path.exists(path): py_files = list(find("*.py", path)) pnames = self._get_plugins_from_list(py_files) if pnames: return pnames return [] def get_modules(self): """Returns the list of importable modules in the configured python package. """ plugins = [] for path in self.package.__path__: if os.path.isdir(path): plugins.extend(self._find_plugins_in_dir(path)) return plugins class TempFileUtil(): def __init__(self, tmp_dir): self.tmp_dir = tmp_dir self.files = [] def new(self): fd, fname = tempfile.mkstemp(dir=self.tmp_dir) # avoid TOCTOU race by using os.fdopen() fobj = os.fdopen(fd, 'w+') self.files.append((fname, fobj)) return fobj def clean(self): for fname, f in self.files: try: f.flush() f.close() except Exception: # file already closed or potentially already removed, ignore pass try: os.unlink(fname) except Exception: # if the above failed, this is also likely to fail, ignore pass self.files = [] class SoSTimeoutError(OSError): pass # vim: set et ts=4 sw=4 : sos-4.8.0/sos/archive.py0000664000175000017500000007073214660147624013577 0ustar arifarif# Copyright (C) 2012 Red Hat, Inc., # Jesse Jaggars # Bryn M. Reeves # # This file is part of the sos project: https://github.com/sosreport/sos # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. import os import tarfile import shutil import logging import codecs import errno import stat import re from datetime import datetime from threading import Lock from importlib.util import find_spec from sos.utilities import sos_get_command_output try: import selinux except ImportError: # not on a distro that uses selinux most likely, but for distros that # use selinux this import failure simply means we won't match context in # the sos archive pass P_FILE = "file" P_LINK = "link" P_NODE = "node" P_DIR = "dir" class Archive: """Abstract base class for archives.""" @classmethod def archive_type(cls): """Returns the archive class's name as a string. """ return cls.__name__ log = logging.getLogger("sos") _name = "unset" _debug = False _path_lock = Lock() def _format_msg(self, msg): return f"[archive:{self.archive_type()}] {msg}" def set_debug(self, debug): self._debug = debug def log_error(self, msg): self.log.error(self._format_msg(msg)) def log_warn(self, msg): self.log.warning(self._format_msg(msg)) def log_info(self, msg): self.log.info(self._format_msg(msg)) def log_debug(self, msg): if not self._debug: return self.log.debug(self._format_msg(msg)) def name(self): return self._name # this is our contract to clients of the Archive class hierarchy. # All sub-classes need to implement these methods (or inherit concrete # implementations from a parent class. def add_file(self, src, dest=None, force=False): raise NotImplementedError def add_string(self, content, dest, mode='w'): raise NotImplementedError def add_binary(self, content, dest): raise NotImplementedError def add_link(self, source, link_name): raise NotImplementedError def add_dir(self, path): raise NotImplementedError def add_node(self, path, mode, device): raise NotImplementedError def get_tmp_dir(self): """Return a temporary directory that clients of the archive may use to write content to. The content of the path is guaranteed to be included in the generated archive.""" raise NotImplementedError def name_max(self): """Return the maximum file name length this archive can support. This is the lesser of the name length limit of the archive format and any temporary file system based cache.""" raise NotImplementedError def get_archive_path(self): """Return a string representing the path to the temporary archive. For archive classes that implement in-line handling this will be the archive file itself. Archives that use a directory based cache prior to packaging should return the path to the temporary directory where the report content is located""" pass def cleanup(self): """Clean up any temporary resources used by an Archive class.""" pass def finalize(self, method): """Finalize an archive object via method. This may involve creating An archive that is subsequently compressed or simply closing an archive that supports in-line handling. If method is automatic then the following methods are tried in order: xz, gzip""" pass class FileCacheArchive(Archive): """ Abstract superclass for archive types that use a temporary cache directory in the file system. """ _tmp_dir = "" _archive_root = "" _archive_name = "" def __init__(self, name, tmpdir, policy, threads, enc_opts, sysroot, manifest=None): self._name = name # truncate the name just relative to the tmpdir in case of full path if os.path.commonprefix([self._name, tmpdir]) == tmpdir: self._name = os.path.relpath(name, tmpdir) self._tmp_dir = tmpdir self._policy = policy self._threads = threads self.enc_opts = enc_opts self.sysroot = sysroot or '/' self.manifest = manifest self._archive_root = os.path.join(tmpdir, name) with self._path_lock: os.makedirs(self._archive_root, 0o700) self.log_info("initialised empty FileCacheArchive at " f"'{self._archive_root}'") def dest_path(self, name): if os.path.isabs(name): name = name.lstrip(os.sep) return os.path.join(self._archive_root, name) def join_sysroot(self, path): if not self.sysroot or path.startswith(self.sysroot): return path if path[0] == os.sep: path = path[1:] return os.path.join(self.sysroot, path) def _make_leading_paths(self, src, mode=0o700): # pylint: disable=too-many-locals """Create leading path components The standard python `os.makedirs` is insufficient for our needs: it will only create directories, and ignores the fact that some path components may be symbolic links. :param src: The source path in the host file system for which leading components should be created, or the path to an sos_* virtual directory inside the archive. Host paths must be absolute (initial '/'), and sos_* directory paths must be a path relative to the root of the archive. :param mode: An optional mode to be used when creating path components. :returns: A rewritten destination path in the case that one or more symbolic links in intermediate components of the path have altered the path destination. """ self.log_debug(f"Making leading paths for {src}") root = self._archive_root dest = src def in_archive(path): """Test whether path ``path`` is inside the archive. """ return path.startswith(os.path.join(root, "")) if not src.startswith("/"): # Sos archive path (sos_commands, sos_logs etc.) src_dir = src else: # Host file path src_dir = (src if os.path.isdir(self.join_sysroot(src)) else os.path.split(src)[0]) # Build a list of path components in root-to-leaf order. path = src_dir path_comps = [] while path not in ('/', ''): head, tail = os.path.split(path) path_comps.append(tail) path = head path_comps.reverse() abs_path = root src_path = "/" # Check and create components as needed for comp in path_comps: abs_path = os.path.join(abs_path, comp) # Do not create components that are above the archive root. if not in_archive(abs_path): continue src_path = os.path.join(src_path, comp) if not os.path.exists(abs_path): self.log_debug(f"Making path {abs_path}") if os.path.islink(src_path) and os.path.isdir(src_path): target = os.readlink(src_path) # The directory containing the source in the host fs, # adjusted for the current level of path creation. target_dir = os.path.split(src_path)[0] # The source path of the target in the host fs to be # recursively copied. target_src = os.path.join(target_dir, target) # Recursively create leading components of target dest = self._make_leading_paths(target_src, mode=mode) dest = os.path.normpath(dest) # In case symlink target is an absolute path, make it # relative to the directory with symlink source if os.path.isabs(target): target = os.path.relpath(target, target_dir) self.log_debug(f"Making symlink '{abs_path}' -> " f"'{target}'") os.symlink(target, abs_path) else: self.log_debug(f"Making directory {abs_path}") os.mkdir(abs_path, mode) dest = src_path return dest def check_path(self, src, path_type, dest=None, force=False): """Check a new destination path in the archive. Since it is possible for multiple plugins to collect the same paths, and since plugins can now run concurrently, it is possible for two threads to race in archive methods: historically the archive class only needed to test for the actual presence of a path, since it was impossible for another `Archive` client to enter the class while another method invocation was being dispatched. Deal with this by implementing a locking scheme for operations that modify the path structure of the archive, and by testing explicitly for conflicts with any existing content at the specified destination path. It is not an error to attempt to create a path that already exists in the archive so long as the type of the object to be added matches the type of object already found at the path. It is an error to attempt to re-create an existing path with a different path type (for example, creating a symbolic link at a path already occupied by a regular file). :param src: the source path to be copied to the archive :param path_type: the type of object to be copied :param dest: an optional destination path :param force: force file creation even if the path exists :returns: An absolute destination path if the path should be copied now or `None` otherwise """ dest = dest or self.dest_path(src) if path_type == P_DIR: dest_dir = dest else: dest_dir = os.path.split(dest)[0] if not dest_dir: return dest # Check containing directory presence and path type if os.path.exists(dest_dir) and not os.path.isdir(dest_dir): raise ValueError(f"path '{dest_dir}' exists and is not a " "directory") if not os.path.exists(dest_dir): src_dir = src if path_type == P_DIR else os.path.split(src)[0] self._make_leading_paths(src_dir) def is_special(mode): return any([ stat.S_ISBLK(mode), stat.S_ISCHR(mode), stat.S_ISFIFO(mode), stat.S_ISSOCK(mode) ]) if force: return dest # Check destination path presence and type if os.path.exists(dest): # Use lstat: we care about the current object, not the referent. st = os.lstat(dest) ve_msg = "path '%s' exists and is not a %s" if path_type == P_FILE and not stat.S_ISREG(st.st_mode): raise ValueError(ve_msg % (dest, "regular file")) if path_type == P_LINK and not stat.S_ISLNK(st.st_mode): raise ValueError(ve_msg % (dest, "symbolic link")) if path_type == P_NODE and not is_special(st.st_mode): raise ValueError(ve_msg % (dest, "special file")) if path_type == P_DIR and not stat.S_ISDIR(st.st_mode): raise ValueError(ve_msg % (dest, "directory")) # Path has already been copied: skip return None return dest def _copy_attributes(self, src, dest): # copy file attributes, skip SELinux xattrs for /sys and /proc try: _stat = os.stat(src) if src.startswith("/sys/") or src.startswith("/proc/"): shutil.copymode(src, dest) os.utime(dest, ns=(_stat.st_atime_ns, _stat.st_mtime_ns)) else: shutil.copystat(src, dest) os.chown(dest, _stat.st_uid, _stat.st_gid) except Exception as e: self.log_debug(f"caught '{e}' setting attributes of '{dest}'") def add_file(self, src, dest=None, force=False): with self._path_lock: if not dest: dest = src dest = self.check_path(dest, P_FILE, force=force) if not dest: return # Handle adding a file from either a string respresenting # a path, or a File object open for reading. if not getattr(src, "read", None): # path case try: shutil.copy(src, dest) except OSError as e: # Filter out IO errors on virtual file systems. if src.startswith("/sys/") or src.startswith("/proc/"): pass else: self.log_info(f"File {src} not collected: '{e}'") self._copy_attributes(src, dest) file_name = f"'{src}'" else: # Open file case: first rewind the file to obtain # everything written to it. src.seek(0) with open(dest, "w", encoding='utf-8') as f: for line in src: f.write(line) file_name = "open file" self.log_debug(f"added {file_name} to FileCacheArchive " f"'{self._archive_root}'") def add_string(self, content, dest, mode='w'): with self._path_lock: src = dest # add_string() is a special case: it must always take precedence # over any exixting content in the archive, since it is used by # the Plugin postprocessing hooks to perform regex substitution # on file content. dest = self.check_path(dest, P_FILE, force=True) with codecs.open(dest, mode, encoding='utf-8') as f: if isinstance(content, bytes): content = content.decode('utf8', 'ignore') f.write(content) if os.path.exists(src): self._copy_attributes(src, dest) self.log_debug(f"added string at '{src}' to FileCacheArchive " f"'{self._archive_root}'") def add_binary(self, content, dest): with self._path_lock: dest = self.check_path(dest, P_FILE) if not dest: return with codecs.open(dest, 'wb', encoding=None) as f: f.write(content) self.log_debug(f"added binary content at '{dest}' to archive " f"'{self._archive_root}'") def add_link(self, source, link_name): self.log_debug(f"adding symlink at '{link_name}' -> '{source}'") with self._path_lock: dest = self.check_path(link_name, P_LINK) if not dest: return if not os.path.lexists(dest): os.symlink(source, dest) self.log_debug(f"added symlink at '{dest}' to '{source}' in " f"archive '{self._archive_root}'") # Follow-up must be outside the path lock: we recurse into # other monitor methods that will attempt to reacquire it. self.log_debug(f"Link follow up: source={source} link_name={link_name}" f" dest={dest}") source_dir = os.path.dirname(link_name) host_path_name = os.path.realpath(os.path.join(source_dir, source)) dest_path_name = self.dest_path(host_path_name) def is_loop(link_name, source): """Return ``True`` if the symbolic link ``link_name`` is part of a file system loop, or ``False`` otherwise. """ link_dir = os.path.dirname(link_name) if not os.path.isabs(source): source = os.path.realpath(os.path.join(link_dir, source)) link_name = os.path.realpath(link_name) # Simple a -> a loop if link_name == source: return True # Find indirect loops (a->b-a) by stat()ing the first step # in the symlink chain try: os.stat(link_name) except OSError as e: if e.errno == 40: return True raise return False if not os.path.exists(dest_path_name): if os.path.islink(host_path_name): # Normalised path for the new link_name link_name = host_path_name # Containing directory for the new link dest_dir = os.path.dirname(link_name) # Relative source path of the new link source = os.path.join(dest_dir, os.readlink(host_path_name)) source = os.path.relpath(source, dest_dir) if is_loop(link_name, source): self.log_debug(f"Link '{link_name}' - '{source}' loops: " "skipping...") return self.log_debug(f"Adding link {link_name} -> {source} for link " "follow up") self.add_link(source, link_name) elif os.path.isdir(host_path_name): self.log_debug(f"Adding dir {source} for link follow up") self.add_dir(host_path_name) elif os.path.isfile(host_path_name): self.log_debug(f"Adding file {source} for link follow up") self.add_file(host_path_name) else: self.log_debug(f"No link follow up: source={source} " f"link_name={link_name}") def add_dir(self, path): """Create a directory in the archive. :param path: the path in the host file system to add """ # Establish path structure with self._path_lock: self.check_path(path, P_DIR) def add_node(self, path, mode, device): dest = self.check_path(path, P_NODE) if not dest: return if not os.path.exists(dest): try: os.mknod(dest, mode, device) except OSError as e: if e.errno == errno.EPERM: msg = "Operation not permitted" self.log_info(f"add_node: {msg} - mknod '{dest}'") return raise e self._copy_attributes(path, dest) def name_max(self): if 'PC_NAME_MAX' in os.pathconf_names: pc_name_max = os.pathconf_names['PC_NAME_MAX'] return os.pathconf(self._archive_root, pc_name_max) return 255 def get_tmp_dir(self): return self._archive_root def get_archive_path(self): return self._archive_root def makedirs(self, path, mode=0o700): """Create path, including leading components. Used by sos.sosreport to set up sos_* directories. """ os.makedirs(os.path.join(self._archive_root, path), mode=mode) self.log_debug(f"created directory at '{path}' in FileCacheArchive " f"'{self._archive_root}'") def open_file(self, path): path = self.dest_path(path) return codecs.open(path, "r", encoding='utf-8', errors='ignore') def cleanup(self): if os.path.isdir(self._archive_root): shutil.rmtree(self._archive_root) def add_final_manifest_data(self, method): """Adds component-agnostic data to the manifest so that individual SoSComponents do not need to redundantly add these manually """ end = datetime.now() start = self.manifest.start_time run_time = end - start self.manifest.add_field('end_time', end) self.manifest.add_field('run_time', run_time) self.manifest.add_field('compression', method) self.add_string(self.manifest.get_json(indent=4), os.path.join('sos_reports', 'manifest.json')) def rename_archive_root(self, cleaner): """Rename the archive to an obfuscated version using an initialized SoSCleaner instance """ self._name = cleaner.obfuscate_string(self._name) _new_root = os.path.join(self._tmp_dir, self._name) os.rename(self._archive_root, _new_root) self._archive_root = _new_root self._archive_name = os.path.join(self._tmp_dir, self.name()) def do_file_sub(self, path, regexp, subst): """Apply a regexp substitution to a file in the archive. :param path: Path in the archive where the file can be found :type path: ``str`` :param regexp: A regex to match the contents of the file :type regexp: ``str`` or compiled ``re`` object :param subst: The substitution string to be used to replace matches within the file :type subst: ``str`` :returns: Number of replacements made :rtype: ``int`` """ common_flags = re.IGNORECASE | re.MULTILINE if hasattr(regexp, "pattern"): pattern = regexp.pattern flags = regexp.flags | common_flags else: pattern = regexp flags = common_flags content = "" with self.open_file(path) as readable: content = readable.read() if not isinstance(content, str): content = content.decode('utf8', 'ignore') result, replacements = re.subn(pattern, subst, content, flags=flags) if replacements: self.add_string(result, path) else: replacements = 0 return replacements def finalize(self, method): self.log_info(f"finalizing archive '{self._archive_root}' using method" f" '{method}'") try: res = self._build_archive(method) except Exception as err: self.log_error(f"An error occurred compressing the archive: {err}") return self.name() self.cleanup() self.log_info(f"built archive at '{self._archive_name}' " f"(size={os.stat(self._archive_name).st_size})") if self.enc_opts['encrypt']: try: return self._encrypt(res) except Exception as e: exp_msg = "An error occurred encrypting the archive:" self.log_error(f"{exp_msg} {e}") return res else: return res def _encrypt(self, archive): """Encrypts the compressed archive using GPG. If encryption fails for any reason, it should be logged by sos but not cause execution to stop. The assumption is that the unencrypted archive would still be of use to the user, and/or that the end user has another means of securing the archive. Returns the name of the encrypted archive, or raises an exception to signal that encryption failed and the unencrypted archive name should be used. """ arc_name = archive.replace("sosreport-", "secured-sosreport-") arc_name += ".gpg" enc_cmd = f"gpg --batch -o {arc_name} " env = None if self.enc_opts["key"]: # need to assume a trusted key here to be able to encrypt the # archive non-interactively enc_cmd += f"--trust-model always -e -r {self.enc_opts['key']} " enc_cmd += archive if self.enc_opts["password"]: # prevent change of gpg options using a long password, but also # prevent the addition of quote characters to the passphrase passwd = self.enc_opts['password'].replace('\'"', '') env = {"sos_gpg": passwd} enc_cmd += "-c --passphrase-fd 0 " enc_cmd = f"/bin/bash -c \"echo $sos_gpg | {enc_cmd}\"" enc_cmd += archive r = sos_get_command_output(enc_cmd, timeout=0, env=env) if r["status"] == 0: return arc_name if r["status"] == 2: if self.enc_opts["key"]: msg = "Specified key not in keyring" else: msg = "Could not read passphrase" else: # TODO: report the actual error from gpg. Currently, we cannot as # sos_get_command_output() does not capture stderr msg = f"gpg exited with code {r['status']}" raise Exception(msg) def _build_archive(self, method): # pylint: disable=unused-argument return self.name() class TarFileArchive(FileCacheArchive): """ archive class using python TarFile to create tar archives""" method = None _with_selinux_context = False def __init__(self, name, tmpdir, policy, threads, enc_opts, sysroot, manifest=None): super().__init__(name, tmpdir, policy, threads, enc_opts, sysroot, manifest) self._suffix = "tar" self._archive_name = os.path.join( tmpdir, self.name() # lgtm [py/init-calls-subclass] ) def set_tarinfo_from_stat(self, tar_info, fstat, mode=None): tar_info.mtime = fstat.st_mtime tar_info.pax_headers['atime'] = f"{fstat.st_atime:.9f}" tar_info.pax_headers['ctime'] = f"{fstat.st_ctime:.9f}" if mode: tar_info.mode = mode else: tar_info.mode = fstat.st_mode tar_info.uid = fstat.st_uid tar_info.gid = fstat.st_gid # this can be used to set permissions if using the # tarfile.add() interface to add directory trees. def copy_permissions_filter(self, tarinfo): orig_path = tarinfo.name[len(os.path.split(self._archive_root)[-1]):] if not orig_path: orig_path = self._archive_root skips = ['/version.txt$', '/sos_logs(/.*)?', '/sos_reports(/.*)?'] if any(re.match(skip, orig_path) for skip in skips): return None try: fstat = os.stat(orig_path) except OSError: return tarinfo if self._with_selinux_context: context = self.get_selinux_context(orig_path) if context: tarinfo.pax_headers['RHT.security.selinux'] = context self.set_tarinfo_from_stat(tarinfo, fstat) return tarinfo def get_selinux_context(self, path): try: (_, c) = selinux.getfilecon(path) return c except Exception: return None def name(self): return f"{self._archive_root}.{self._suffix}" def _build_archive(self, method): if method == 'auto': method = 'xz' if find_spec('lzma') is not None else 'gzip' _comp_mode = method.strip('ip') self._archive_name = f"{self._archive_name}.{_comp_mode}" # tarfile does not currently have a consistent way to define comnpress # level for both xz and gzip ('preset' for xz, 'compresslevel' for gz) if method == 'gzip': kwargs = {'compresslevel': 6} else: kwargs = {'preset': 3} with tarfile.open(self._archive_name, mode=f"w:{_comp_mode}", **kwargs) as tar: # Add commonly reviewed files first, so that they can be more # easily read from memory without needing to extract # the whole archive for _content in ['version.txt', 'sos_reports', 'sos_logs']: if os.path.exists(os.path.join(self._archive_root, _content)): tar.add( os.path.join(self._archive_root, _content), arcname=f"{self._name}/{_content}" ) # we need to pass the absolute path to the archive root but we # want the names used in the archive to be relative. tar.add(self._archive_root, arcname=self._name, filter=self.copy_permissions_filter) self._suffix += f".{_comp_mode}" return self.name() # vim: set et ts=4 sw=4 : sos-4.8.0/.editorconfig0000664000175000017500000000015314660147624013443 0ustar arifarifroot = true [*] end_of_line = lf insert_final_newline = true [*.py] indent_style = space indent_size = 4 sos-4.8.0/.packit.yaml0000664000175000017500000000100314660147624013176 0ustar arifarifupstream_project_url: https://github.com/sosreport/sos specfile_path: sos.spec downstream_package_name: sos upstream_package_name: sos files_to_sync: - sos.spec - .packit.yaml srpm_build_deps: - python3-devel - gettext jobs: - job: copr_build trigger: pull_request targets: - fedora-development-x86_64 - fedora-development-aarch64 - fedora-development-ppc64le - fedora-development-s390x - centos-stream-9 notifications: pull_request: successful_build: true sos-4.8.0/setup.py0000664000175000017500000000244614660147624012507 0ustar arifarif#!/usr/bin/env python from setuptools import setup, find_packages from sos import __version__ as VERSION setup( name='sos', version=VERSION, # to avoid a packaging dependency on older RHELs # we only declare it on recent Python versions install_requires=['pexpect', 'pyyaml', 'packaging;python_version>="3.11"'], description=( 'A set of tools to gather troubleshooting information from a system' ), author='Bryn M. Reeves', author_email='bmr@redhat.com', maintainer='Jake Hunsaker', maintainer_email='jhunsake@redhat.com', url='https://github.com/sosreport/sos', license="GPLv2+", scripts=['bin/sos', 'bin/sosreport', 'bin/sos-collector'], data_files=[ ('share/man/man1', ['man/en/sosreport.1', 'man/en/sos-report.1', 'man/en/sos.1', 'man/en/sos-collect.1', 'man/en/sos-collector.1', 'man/en/sos-clean.1', 'man/en/sos-mask.1', 'man/en/sos-help.1']), ('share/man/man5', ['man/en/sos.conf.5']), ('share/licenses/sos', ['LICENSE']), ('share/doc/sos', ['AUTHORS', 'README.md']), ('config', ['sos.conf', 'tmpfiles/tmpfilesd-sos-rh.conf']) ], packages=find_packages(include=['sos', 'sos.*']) ) # vim: set et ts=4 sw=4 : sos-4.8.0/requirements.txt0000664000175000017500000000012214660147624014246 0ustar arifarifpycodestyle>=2.4.0 coverage>=4.0.3 Sphinx>=1.3.5 pexpect>=4.0.0 pyyaml packaging sos-4.8.0/MANIFEST.in0000664000175000017500000000002014660147624012515 0ustar arifarifinclude po/*.po sos-4.8.0/pylintrc0000664000175000017500000000300414660147624012553 0ustar arifarif[MAIN] # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. jobs=0 # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes [FORMAT] # Maximum number of characters on a single line. max-line-length=79 [REPORTS] # Activate the evaluation score. score=yes [MESSAGES CONTROL] disable= C0103, # invalid-name C0114, # missing-module-docstring C0115, # missing-class-docstring C0116, # missing-function-docstring C0302, # too-many-lines C0415, # import-outside-toplevel R0401, # cyclic-import R0801, # duplicate-code R0902, # too-many-instance-attributes R0903, # too-few-public-methods R0904, # too-many-public-methods R0911, # too-many-return-statements R0912, # too-many-branches R0913, # too-many-arguments R0914, # too-many-locals R0915, # too-many-statements R1702, # too-many-nested-blocks W0201, # attribute-defined-outside-init W0511, # fixme ###################### VV things we should fix VV W0719, # broad-exception-raised W1203, # logging-fstring-interpolation W0107, # unnecessary-pass W0718, # broad-exception-caught W0102, # dangerous-default-value C0201, # consider-iterating-dictionary W1201, # logging-not-lazy W0707, # raise-missing-from W0212, # protected-access W0223, # abstract-method W1509, # subprocess-popen-preexec-fn sos-4.8.0/tox.ini0000664000175000017500000000261514660147624012306 0ustar arifarif[tox] envlist = flake8 [testenv] deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt python_magic setenv = PYTHONPATH = {toxinidir}/tests:{toxinidir} avocado_cmd = avocado run -p TESTLOCAL=true --max-parallel-tasks=1 stage_tests = {toxinidir}/tests/cleaner_tests \ {toxinidir}/tests/collect_tests \ {toxinidir}/tests/report_tests \ {toxinidir}/tests/vendor_tests py_files = setup.py plugins_overview.py sos tests foreman_tests = {toxinidir}/tests/product_tests/foreman [testenv:flake8] deps = flake8 commands = flake8 {posargs:{[testenv]py_files}} [testenv:pylint] deps = {[testenv]deps} pylint commands = pylint -v --rcfile={toxinidir}/pylintrc {posargs:{[testenv]py_files}} [testenv:unit_tests] basepython = python3 commands = avocado run tests/unittests [testenv:stageone_tests] basepython = python3 commands = {[testenv]avocado_cmd} -t stageone {posargs:{[testenv]stage_tests}} [testenv:stagetwo_tests] basepython = python3 sitepackages = True commands = {[testenv]avocado_cmd} -t stagetwo {posargs:{[testenv]stage_tests}} [testenv:foreman_tests] basepython = python3 commands = {[testenv]avocado_cmd} -t foreman {posargs:{[testenv]foreman_tests}} [testenv:nosetests] basepython = python3 deps = {[testenv]deps} nose3 commands = nosetests -v --with-coverage --cover-package=sos tests/unittests --cover-html sos-4.8.0/debian/0000775000175000017500000000000014660147624012211 5ustar arifarifsos-4.8.0/.github/0000775000175000017500000000000014660147624012327 5ustar arifarifsos-4.8.0/.github/PULL_REQUEST_TEMPLATE.md0000664000175000017500000000172214660147624016132 0ustar arifarif--- Please place an 'X' inside each '[]' to confirm you adhere to our [Contributor Guidelines](https://github.com/sosreport/sos/wiki/Contribution-Guidelines) - [ ] Is the commit message split over multiple lines and hard-wrapped at 72 characters? - [ ] Is the subject and message clear and concise? - [ ] Does the subject start with **[plugin_name]** if submitting a plugin patch or a **[section_name]** if part of the core sosreport code? - [ ] Does the commit contain a **Signed-off-by: First Lastname **? - [ ] Are any related Issues or existing PRs [properly referenced](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) via a Closes (Issue) or Resolved (PR) line? - [ ] Are all passwords or private data gathered by this PR [obfuscated](https://github.com/sosreport/sos/wiki/How-to-Write-a-Plugin#how-to-prevent-collecting-passwords)? sos-4.8.0/.github/codeql/0000775000175000017500000000000014660147624013576 5ustar arifarifsos-4.8.0/.github/codeql/codeql-config.yaml0000664000175000017500000000005214660147624017171 0ustar arifarifname: "SoS CodeQL Config" paths: - sos sos-4.8.0/.github/workflows/0000775000175000017500000000000014660147624014364 5ustar arifarifsos-4.8.0/.github/workflows/codeql.yaml0000664000175000017500000000157314660147624016525 0ustar arifarifname: "CodeQL" on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - cron: "49 12 * * 6" jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ python ] steps: - name: Checkout uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: config-file: .github/codeql/codeql-config.yaml languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" sos-4.8.0/.github/workflows/snap.yaml0000664000175000017500000000216614660147624016216 0ustar arifarifname: snap on: push: branches: - main release: types: - published jobs: build: runs-on: ubuntu-22.04 concurrency: group: snap-build cancel-in-progress: true steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: snapcore/action-build@v1 id: build-snap # Make sure the snap is installable - run: | sudo apt -y remove sosreport sudo snap install --classic --dangerous ${{ steps.build-snap.outputs.snap }} sudo snap alias sosreport.sos sos # Do some testing with the snap - run: | sudo sos help - uses: snapcore/action-publish@v1 if: ${{ github.event_name == 'push' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }} with: snap: ${{ steps.build-snap.outputs.snap }} release: "latest/edge" - uses: snapcore/action-publish@v1 if: ${{ github.event_name == 'release' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }} with: snap: ${{ steps.build-snap.outputs.snap }} release: "latest/candidate" sos-4.8.0/README.md0000664000175000017500000001051414660147624012247 0ustar arifarif[![Build Status](https://api.cirrus-ci.com/github/sosreport/sos.svg?branch=main)](https://cirrus-ci.com/github/sosreport/sos) [![Documentation Status](https://readthedocs.org/projects/sos/badge/?version=main)](https://sos.readthedocs.io/en/main/?badge=main) [![sosreport](https://snapcraft.io/sosreport/badge.svg)](https://snapcraft.io/sosreport) # SoS Sos is an extensible, portable, support data collection tool primarily aimed at Linux distributions and other UNIX-like operating systems. This project is hosted at: * https://github.com/sosreport/sos For the latest version, to contribute, and for more information, please visit the project pages or join the mailing list. To clone the current main (development) branch run: ``` git clone git://github.com/sosreport/sos.git ``` ## Reporting bugs Please report bugs via the mailing list or by opening an issue in the [GitHub Issue Tracker][5] ## Chat The SoS project has rooms in Matrix and in Libera.Chat. Matrix Room: #sosreport:matrix.org Libera.Chat: #sos These rooms are bridged, so joining either is sufficient as messages from either will appear in both. The Freenode #sos room **is no longer used by this project**. ## Mailing list The [sos-devel][4] list is the mailing list for any sos-related questions and discussion. Patch submissions and reviews are welcome too. ## Patches and pull requests Patches can be submitted via the mailing list or as GitHub pull requests. If using GitHub please make sure your branch applies to the current main branch as a 'fast forward' merge (i.e. without creating a merge commit). Use the `git rebase` command to update your branch to the current main if necessary. Please refer to the [contributor guidelines][0] for guidance on formatting patches and commit messages. Before sending a [pull request][0], it is advisable to check your contribution against the `flake8` linter, the unit tests, and the stage one avocado test suite: ``` # from within the git checkout $ flake8 sos $ nosetests -v tests/unittests/ # as root # PYTHONPATH=tests/ avocado run --test-runner=runner -t stageone tests/{cleaner,collect,report,vendor}_tests ``` Note that the avocado test suite will generate and remove several reports over its execution, but no changes will be made to your local system. All contributions must pass the entire test suite before being accepted. ## Documentation User and API [documentation][6] is automatically generated using [Sphinx][7] and [Read the Docs][8]. To generate HTML documents locally, install dependencies using ``` pip install -r requirements.txt ``` and run ``` sphinx-build -b html docs ``` ### Wiki For more in-depth information on the project's features and functionality, please see [the GitHub wiki][9]. If you are interested in contributing an entirely new plugin, or extending sos to support your distribution of choice, please see these wiki pages: * [How to write a plugin][1] * [How to write a policy][2] * [Plugin options][3] To help get your changes merged quickly with as few revisions as possible please refer to the [Contributor Guidelines][0] when submitting patches or pull requests. ## Installation ### Manual Installation You can simply run from the git checkout now: ``` $ sudo ./bin/sos report ``` The command `sosreport` is still available, as a legacy redirector, and can be used like this: ``` $ sudo ./bin/sosreport ``` To see a list of all available plugins and plugin options, run ``` $ sudo ./bin/sos report -l ``` To install locally (as root): ``` # python3 setup.py install ``` ### Pre-built Packaging Fedora/RHEL users install via yum: ``` # yum install sos ``` Debian users install via apt: ``` # apt install sosreport ``` Ubuntu (14.04 LTS and above) users install via apt: ``` # sudo apt install sosreport ``` ### Snap Installation ``` # snap install sosreport --classic ``` [0]: https://github.com/sosreport/sos/wiki/Contribution-Guidelines [1]: https://github.com/sosreport/sos/wiki/How-to-Write-a-Plugin [2]: https://github.com/sosreport/sos/wiki/How-to-Write-a-Policy [3]: https://github.com/sosreport/sos/wiki/Plugin-options [4]: https://www.redhat.com/mailman/listinfo/sos-devel [5]: https://github.com/sosreport/sos/issues?state=open [6]: https://sos.readthedocs.org/ [7]: https://www.sphinx-doc.org/ [8]: https://www.readthedocs.org/ [9]: https://github.com/sosreport/sos/wiki sos-4.8.0/AUTHORS0000664000175000017500000001200414660147624012034 0ustar arifarifIndividuals ----------- Aaron Conole Abhijeet Kasurde Adam Stokes Adrien Kunysz Alan Pevec Alexandru Juncu Ante Karamatic Archit Sharma Aruna Balakrishnaiah Assaf Muller Ben Turner Bharani C.V. Bill Yodlowsky Brent Eagles Bryan Quigley Bryn M. Reeves Chris J Arges Chris Johnston Chris Newcomer Christy Perez Coty Sutherland Dimitri John Ledkov Eduardo Damato Edward Hope-Morley Eoghan Glynn Eric Desrochers Eric Rich Eric Williams Eugene Teo Felipe Reyes Flaper Fesp Flavio Leitner Flavio Percoco Frank Ch. Eigler Gary Kotton Gaël Chamoulaud Germano Veit Michel Guy Streeter Günther Deschner Harald Klein Hisanobu Okuda Jacob Wen Jake Hunsaker James Hunt Jan Grant Jan Pokorný Jeff Dutton Jeff Peeler Jeff Welch Jeremy Agee Jeremy Crafts Jeremy Eder Jesse Jaggars Jian Wen Jiri Popelka Jirka Hladky Joel Stanley Joey Boggs John Berninger John Haxby Jon Magrini Jon Stanley Jorge Niedbalski Jose Castillo Justin Payne Justin Stephenson Kamalesh Babulal Keigo Noha Keith Kearnan Keith Robertson Kenneth Koski Kent Lamb Kevin Traynor Lee Yarwood Leno Hou Louis Bouchard Luca Miccini Luigi Toscano Lukas Herbolt Lukas Zapletal Major Hayden Marc Sauton Martin Frodl Martin Schuppert Michael Adam Michael Kerrin Michal Srb Michele Baldessari Mukesh Ojha Navid Sheikhol-Eslami Neependra Khare Nijin Ashok Pablo Iranzo Gómez Patrick Talbert Pavel Moravec Pep Turro Mauri Peter Portante Pierguido Lambri Pierre Amadio Pierre Carrier Piotr Drąg Poornima Pratik Bandarkar Ranjith Rajaram Raphael Badin Richard Brantley Robb Manes Rohan Kanade Sachin Sadique Puthen Samuel Mendoza-Jonas Sandro Bonazzola Shane Bradley Shijoe George Soumya Koduri Steve Conklin Tim Speetjens Tomas Petr Tomas Smetana Tomas Tomecek Vasant Hegde Xavier Queralt Yedidyah Bar David amitg.b14@gmail.com galstrom21 hari gowtham jbainbri jhjaggars mulhern ncoghlan qsn spandey tanaka_733 tiwillia@redhat.com Companies --------- Red Hat, Inc. Rackspace US, Inc. EMC Corporation Canonical, Ltd. IBM Corporation Hewlett-Packard Development Company, L.P. Oracle Corporation Organizations ------------- The Linux Foundation Sambasos-4.8.0/man/0000775000175000017500000000000014660147624011542 5ustar arifarifsos-4.8.0/man/en/0000775000175000017500000000000014660147624012144 5ustar arifarifsos-4.8.0/man/en/sos-clean.10000664000175000017500000001537114660147624014121 0ustar arifarif.TH SOS_CLEAN 1 "Thu May 21 2020" .SH NAME sos_clean, sos_mask \- Obfuscate sensitive data from one or more sos reports .SH SYNOPSIS .B sos clean TARGET [options] [\-\-domains] [\-\-disable-parsers] [\-\-skip-cleaning-files|\-\-skip-masking-files] [\-\-keywords] [\-\-keyword-file] [\-\-map-file] [\-\-jobs] [\-\-no-update] [\-\-keep-binary-files] [\-\-archive-type] .SH DESCRIPTION \fBsos clean\fR or \fBsos mask\fR is an sos subcommand used to obfuscate sensitive information from previously generated sos reports that is not covered by the standard plugin-based post processing executed during report generation, for example IP addresses. .LP Data obfuscated via this utility is done so consistently, meaning for example an IP address of 192.168.1.1 in an unprocessed sos report that gets obfuscated to, for example, 100.0.0.1, will be changed to 100.0.0.1 in all occurrences found in the report. Additionally, by default all such obfuscations are stored in "maps" that will be persistently saved to /etc/sos/cleaner/default_mapping and be re-used on subsequent runs. .LP This utility may also be used in-line with \fBsos report\fR and \fB sos collect\fR by specifying the \fB\-\-clean\fR or \fB\-\-mask\fR option. .LP When called directly via \fBsos clean\fR, the obfuscated archive is written as an additional file, meaning the original unprocessed report still remains on the filesystem. When called via \fBreport\fR or \fBcollect\fR, the changes are done in-line and thus only an obfuscated archive is written and available. In either case, a mapping file containing the relationships between unprocessed and obfuscated elements will be written in the same location as the resulting archive. This mapping file should be kept private by system administrators. .SH REQUIRED ARGUMENTS .B TARGET .TP The path to the archive that is to be obfuscated. This may be an archive or an unbuilt sos temporary directory. If an archive, it will first be extracted and then after obfuscation is complete re-compressed using the same compression method as the original. .SH OPTIONS .TP .B \-\-domains DOMAINS Provide a comma-delimited list of domain names to obfuscate, in addition to those matching the hostname of the system that created the sos report. Subdomains that match a domain given via this option will also be obfuscated. For example, if \fB\-\-domains redhat.com\fR is specified, then 'redhat.com' will be obfuscated, as will 'www.redhat.com' and subdomains such as 'foo.redhat.com'. .TP .B \-\-disable-parsers PARSERS Provide a comma-delimited list of parsers to disable when cleaning an archive. By default all parsers are enabled. Note that using this option is very likely to leave sensitive information in place in the target archive, so only use this option when absolutely necessary or you have complete trust in the party/parties that may handle the generated report. Valid values for this option are currently: \fBhostname\fR, \fBip\fR, \fBipv6\fR, \fBmac\fR, \fBkeyword\fR, and \fBusername\fR. .TP .B \-\-skip-cleaning-files, \-\-skip-masking-files FILES Provide a comma-delimited list of files inside an archive, that cleaner should skip in cleaning. Globs like asterisk are supported, so \fBsos_commands/host/hostname*\fR will match all three usual filenames in that directory (\fBhostname\fR, \fBhostnamectl_status\fR and \fBhostname_-f\fR). Use this option with caution, only when being certain the given files do not contain any sensitive information. .TP .B \-\-keywords KEYWORDS Provide a comma-delimited list of keywords to scrub in addition to the default parsers. Keywords provided by this option will be obfuscated as "obfuscatedwordX" where X is an integer based on the keyword's index in the parser. Note that keywords will be replaced as both standalone words and in substring matches. .TP .B \-\-keyword-file FILE Provide a file that contains a list of keywords that should be obfuscated. Each word must be specified on a newline within the file. .TP .B \-\-map-file FILE Provide a location to a valid mapping file to use as a reference for existing obfuscation pairs. If one is found, the contents are loaded before parsing is started. This allows consistency between runs of this command for obfuscated pairs. By default, sos will write the generated private map file to /etc/sos/cleaner/default_mapping so that consistency is maintained by default. Users may use this option to reference a map file from a different run (perhaps one that was done on another system). Default: /etc/sos/cleaner/default_mapping .TP .B \-\-jobs JOBS The number of concurrent archives to process, if more than one. If this utility is called by \fBsos collect\fR then the value of the jobs option for that utility will be used here. Default: 4 .TP .B \-\-no-update Do not write the mapping file contents to /etc/sos/cleaner/default_mapping .TP .B \-\-keep-binary-files Keep unprocessable binary files in the archive, rather than removing them. Note that binary files cannot be obfuscated, and thus keeping them in the archive may result in otherwise sensitive information being included in the final archive. Users should review any archive that keeps binary files in place before sending to a third party. Default: False (remove encountered binary files) .TP .B \-\-archive-type TYPE Specify the type of archive that TARGET was generated as. When sos inspects a TARGET archive, it tries to identify what type of archive it is. For example, it may be a report generated by \fBsos report\fR, or a collection of those reports generated by \fBsos collect\fR, which require separate approaches. This option may be useful if a given TARGET archive is known to be of a specific type, but due to unknown reasons or some malformed/missing information in the archive directly, that is not properly identified by sos. The following are accepted values for this option: \fBauto\fR Automatically detect the archive type \fBreport\fR An archive generated by \fBsos report\fR \fBcollect\fR An archive generated by \fBsos collect\fR \fBinsights\fR An archive generated by the \fBinsights-client\fR package The following may also be used, however note that these do not attempt to pre-load any information from the archives into the parsers. This means that, among other limitations, items like host and domain names may not be obfuscated unless an obfuscated mapping already exists on the system from a previous execution. \fBdata-dir\fR A plain directory on the filesystem. \fBtarball\fR A generic tar archive not associated with any known tool .SH SEE ALSO .BR sos (1) .BR sos-report (1) .BR sos-collect (1) .BR sos.conf (5) .SH MAINTAINER .nf Maintained on GitHub at https://github.com/sosreport/sos .fi .SH AUTHORS & CONTRIBUTORS See \fBAUTHORS\fR file in the package documentation. sos-4.8.0/man/en/sos-mask.10000777000175000017500000000000014660147624015724 2sos-clean.1ustar arifarifsos-4.8.0/man/en/sos-help.10000664000175000017500000000343614660147624013766 0ustar arifarif.TH SOS_HELP 1 "Fri Nov 05 2021" .SH NAME sos_help \- get detailed help information on sos commands and components .SH SYNOPSIS .B sos help TOPIC .SH DESCRIPTION \fBsos help\fR is used to retrieve more detailed information on the various SoS commands and components than is directly available in either other manpages or --help output. This information could for example be investigating a specific plugin to learn more about its purpose, use case, collections, available plugin options, edge cases, and more. .LP Most aspects of SoS' operation can be investigated this way - the top level functions such as \fB report, clean,\fR and \fBcollect\fR, as well as constructs that allow those functions to work; e.g. \fBtransports\fR within \fBsos collect\fR that define how that function connects to remote nodes. .SH REQUIRED ARGUMENTS .B TOPIC .TP The section or topic to retrieve detailed help information for. TOPIC takes the general form of \fBcommand.component.entity\fR, with \fBcomponent\fR and \fBentity\fR being optional. .LP Top-level \fBcommand\fR help sections will often direct users to \fBcomponent\fR sections which in turn may point to further \fBentity\fR subsections. Some of the more useful or interesting sections are listed below: \fBTopic\fR \fBDescription\fR \fBreport\fR The \fBsos report\fR command \fBreport.plugins\fR Information on what report plugins are \fBreport.plugins.$plugin\fR Information on a specific plugin \fBclean\fR or \fBmask\fR The \fBsos clean|mask\fR command \fBcollect\fR The \fBsos collect\fR command \fBcollect.clusters\fR How \fBcollect\fR enumerates nodes in a cluster \fBpolicies\fR How SoS behaves on different distributions sos-4.8.0/man/en/sos.10000664000175000017500000001550614660147624013041 0ustar arifarif.TH sos 1 "April 2020" .SH NAME sos \- A unified tool for collecting system logs and other debug information .SH SYNOPSIS \fBsos\fR component [options] .SH DESCRIPTION \fBsos\fR is a diagnostic data collection utility, used by system administrators, support representatives, and the like to assist in troubleshooting issues with a system or group of systems. The most well known function is \fB sos report\fR or \fBsos report\fR as it was previously known. An sos archive is typically requested by support organizations to collect baseline configuration and system data from which to begin the troubleshooting process. .SH COMPONENTS sos supports several subcommands or components. Each provides a different set of information for the user. Supported components are as follows .TP .B report Report generates an archive of system information including configuration files and command output. Information included in the report is based upon plugins that are activated automatically when certain criteria, such as installed packages, files, services, or system architecture is detected. See \fBsos report --help\fR and \fBman sos-report\fR for more information. May also be invoked via the alias \fBrep\fR or the deprecated command \fBsos report\fR. .TP .B collect Collect is used to capture reports on multiple systems simultaneously. These systems can either be defined by the user at the command line and/or defined by clustering software that exists either on the local system or on a "primary" system that is able to inform about other nodes in the cluster. When running collect, sos report will be run on the remote nodes, and then the resulting archives will be copied from those nodes to the local system running sos collect. Archives are then removed from the remote systems. See \fBsos collect --help\fR and \fBman sos-collect\fR for more information. May also be invoked via the alias \fBsos collector\fR or the deprecated command \fBsos-collector\fR. .TP .B clean|cleaner|mask This subcommand takes input of either 1) an sos report tarball, 2) a collection of sos report tarballs such as from \fBcollect\fR, or 3) the unpackaged directory of an sos report and obfuscates potentially sensitive system information that is not covered by the standard postprocessing of \fBsos report\fR. Such data includes IP addresses, networks, MAC addresses, and more. Data obfuscated by this command will remain consistent throughout the report and across reports provided in the same invocation. Additionally, care is taken to maintain network topology relationships between matched data items. See \fB sos clean --help\fR and \fBman sos-clean\fR for more information. May be invoked via either \fBsos clean\fR, \fBsos cleaner\fR, \fBsos mask\fR, or via the \fB--clean\fR, \fB--cleaner\fR or \fB --mask\fR options for \fBreport\fR and \fBcollect\fR. .TP .B help This subcommand is used to retrieve more detailed information on the various SoS commands and components than is directly available in either other manpages or --help output. See \fB sos help --help\fR and \fB man sos-help\fR for more information. .SH GLOBAL OPTIONS sos components provide their own set of options, however the following are available to be set across all components. .B \-\-batch Do not prompt interactively, user will not be prompted for any data .TP .B \-\-encrypt Encrypt the resulting archive, and determine the method by which that encryption is done by either a user prompt or environment variables. When run with \fB--batch\fR, using this option will cause sos to look for either the \fBSOSENCRYPTKEY\fR or \fBSOSENCRYPTPASS\fR environment variables. If set, this will implicitly enable the \fB--encrypt-key\fR or \fB--encrypt-pass\fR options, respectively, to the values set by the environment variable. This enables the use of these options without directly setting those options in a config file or command line string. Note that use of an encryption key has precedence over a passphrase. Otherwise, using this option will cause sos to prompt the user to choose the method of encryption to use. Choices will be [P]assphrase, [K]ey, [E]nv vars, or [N]o encryption. If passphrase or key the user will then be prompted for the respective value, env vars will cause sos to source the information in the manner stated above, and choosing no encryption will disable encryption. See the sections on \fB--encrypt-key\fR and \fB--encrypt-pass\fR below for more information. .TP .B \--encrypt-key KEY Encrypts the resulting archive that sos report produces using GPG. KEY must be an existing key in the user's keyring as GPG does not allow for keyfiles. KEY can be any value accepted by gpg's 'recipient' option. Note that the user running sos report must match the user owning the keyring from which keys will be obtained. In particular this means that if sudo is used to run sos report, the keyring must also be set up using sudo (or direct shell access to the account). Users should be aware that encrypting the final archive will result in sos using double the amount of temporary disk space - the encrypted archive must be written as a separate, rather than replacement, file within the temp directory that sos writes the archive to. However, since the encrypted archive will be the same size as the original archive, there is no additional space consumption once the temporary directory is removed at the end of execution. This means that only the encrypted archive is present on disk after sos finishes running. If encryption fails for any reason, the original unencrypted archive is preserved instead. .TP .B \--encrypt-pass PASS The same as \--encrypt-key, but use the provided PASS for symmetric encryption rather than key-pair encryption. .TP .B \--config-file CONFIG Specify alternate configuration file. .TP .B \-s, \--sysroot SYSROOT Specify an alternate root file system path. .TP .B \--tmp-dir DIRECTORY Specify alternate temporary directory to copy data during execution. .TP .B \--threads THREADS Specify the number of threads sos report will use for concurrency. Defaults to 4. .TP .B \-v, \--verbose Increase logging verbosity. May be specified multiple times to enable additional debugging messages. The following table summarizes the effects of different verbosity levels: 1 (-v) : Enable debug messages for sos.log. Show individual plugins starting. 2 (-vv) : Also print debug messages to console. 3 (-vvv) : Enable debug messages for archive file operations. Note this will dramatically increase the amount of logging. .TP .B \-q, \--quiet Only log fatal errors to stderr. .TP .B \-z, \-\-compression-type {auto|xz|gzip} Compression type to use when compression the final archive output .TP .B \--help Display usage message. .SH SEE ALSO .BR sos.conf (5) .SH MAINTAINER .nf Maintained on GitHub at https://github.com/sos report/sos .fi .SH AUTHORS & CONTRIBUTORS See \fBAUTHORS\fR file in the package documentation. sos-4.8.0/man/en/sos.conf.50000664000175000017500000001063614660147624013770 0ustar arifarif.TH "sos.conf" "5" "SOS" "sos configuration file" .SH NAME sos.conf \- sos report configuration .SH DESCRIPTION .sp sos report uses a configuration file at /etc/sos/sos.conf, and there are subdirectories under /etc/sos that are used for specific purposes. Note that non-root users may override options set in /etc/sos/sos.conf by creating their own sos.conf under $HOME/.config/sos. The order in which options are loaded is as follows: 1. System configuration file at /etc/sos/sos.conf 2. User-specific configuration file at $HOME/.config/sos/sos.conf (for sos components that support non-root) 3. In the case of running \fBsos report\fR, presets either automatically loaded due to system configuration, or specified via \fB--preset\fR 4. Command line values In other words, config files will override defaults, presets override config files, and command line values override presets and config files. .SH SUBDIRECTORIES The following subdirectories exist under /etc/sos and are used as noted below .TP \fBextras.d\fP This directory is used to store configuration files used by the sos_extras plugin. The plugin traverses this directory and for each file there it executes commands or collects files optionally with sizelimit. Expected content of an extras file is as follows: - empty lines or those starting with '#' are ignored - add_copy_spec called to lines starting by ':', optionally followed by sizelimit - otherwise, whole line will be executed as a command. Example: command1 --arg1 val1 command2 :/path/to/file :/path/to/files* sizelimit WARNING: be careful what files to collect or what commands to execute: - avoid calling potentially dangerous or system altering commands, like: - using multiple commands on a line (via pipes, semicolon etc.) - executing commands on background - setting env.variables (as those will be ignored) - altering a system (not only by "rm -rf") - be aware, no secret obfuscation is made .TP \fBgroups.d\fP This directory is used to store host group configuration files for \fBsos collect\fP. These files can specify any/all of the \fBprimary\fP, \fBnodes\fP, and \fBcluster-type\fP options. Users may create their own private host groups in $HOME/.config/sos/groups.d/. If a host group of the same name is saved in both the user's homedir and this directory, the homedir configuration file will have precedence. When run as non-root, \fBsos collect\fP will save host groups to the user's home dir, and create the necessary directory structure if required. Note that non-root users may load host groups defined under /etc/sos/groups.d/, but they may not write new groups or update existing groups saved there. .TP \fBpresets.d\fP This directory is used to store preset configuration files for \fBsos report\fP. Presets may be used to save standard sets of options. See \fBman sos-report\fP for more information. .SH PARAMETERS .sp There are sections for each sos component, as well as global values and those for plugin options. Options are set using 'ini'-style \fBname = value\fP pairs. Disabling/enabling a boolean option is done the same way like on command line (e.g. process.lsof=off). Some options accept a comma separated list of values. Using options that don't expect a value (like all-logs or no-report) will result in enabling those options, regardless of value set. Sections are parsed in the ordering: .br - \fB[global]\fP .br - \fB[component]\fP .br - \fB[plugin_options]\fP .TP \fB[global]\fP