Skip to content
This repository has been archived by the owner on Jun 29, 2022. It is now read-only.

Commit

Permalink
Merge pull request #156 from /issues/80
Browse files Browse the repository at this point in the history
Add 'rho fact list' command.
  • Loading branch information
noahl committed Aug 9, 2017
2 parents 6825f68 + 659c8e9 commit 2c1d5e3
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 5 deletions.
16 changes: 16 additions & 0 deletions CONTRIBUTING.rst
Expand Up @@ -70,6 +70,22 @@ You should also add tests for any new features and bug fixes.

rho uses `pytest`_ for testing.

Adding Facts
------------

If you want to make a change that affects the documentation of
particular facts,

1. Make the change in `doc/command_syntax_usage.rst`
2. Run `make gen-python-docs` to update `rho/fact_docs.py`
3. Check in the new version of `rho/fact_docs.py` with your change

Rho keeps fact documentation in two places - in
`docs/command_syntax_usage.rst`, and in `rho/fact_docs.py` for use by
the program at runtime. The second is generated from the first at
build time rather than runtime to minimize the number of things that
can go wrong at runtime.


Running all tests:
******************
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Expand Up @@ -22,6 +22,10 @@ all: build lint tests-coverage
docs:
@cd doc; $(MAKE) gen-api; $(MAKE) html; $(MAKE) nojekyll

gen-python-docs:
@cd doc; $(MAKE) gen-python
mv doc/fact_docs.py rho

build: clean
$(PYTHON) setup.py build -f

Expand All @@ -44,7 +48,7 @@ tests-coverage:
-py.test -v --cov=rho --cov=library

lint-flake8:
flake8 . --ignore D203
flake8 . --ignore D203 --exclude fact_docs.py

lint-pylint:
pylint --disable=duplicate-code */*.py
Expand All @@ -61,7 +65,7 @@ tests-coverage-3:
-py.test-3 -v --cov=rho --cov=library

lint-flake8-3:
python3 -m flake8 . --ignore D203
python3 -m flake8 . --ignore D203 --exclude fact_docs.py

lint-pylint-3:
python3 -m pylint --disable=duplicate-code,locally-disabled */*.py -rn
Expand Down
3 changes: 3 additions & 0 deletions dev-requirements.txt
Expand Up @@ -16,3 +16,6 @@ Coverage

# Needed for StringIO
six

# Fact documentation
docutils
3 changes: 3 additions & 0 deletions doc/Makefile
Expand Up @@ -24,6 +24,9 @@ gen-api: clean
nojekyll:
touch $(BUILDDIR)/html/.nojekyll

gen-python:
./generate_python_docs.py

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
Expand Down
7 changes: 4 additions & 3 deletions doc/command_syntax_usage.rst
Expand Up @@ -5,7 +5,7 @@ The basic syntax is:

``rho command subcommand [options]``

There are eight rho commands:
There are nine rho commands:
* ``auth`` - for managing auth entries
* ``profile`` - for managing profile entries
* ``scan`` - for running scans
Expand All @@ -14,6 +14,7 @@ There are eight rho commands:
* ``edit`` - to modify an existing entry
* ``clear`` - to remove any or all entries
* ``show`` and ``list`` - to display one or more entries
* ``fact`` - to show information about the facts rho can collect

The complete list of options for each command and subcommand are listed in the
rho manpage with other usage examples. The common options are listed with the
Expand Down Expand Up @@ -164,9 +165,9 @@ scan identified by the timestamp.
For further details of the command usage view the following
`example <command_example.rst>`_.

^^^^^^^
^^^^^^^^^^^^^^^^^^^^^
Scan User Permissions
^^^^^^^
^^^^^^^^^^^^^^^^^^^^^
Some of the output facts will report an error if the user used to perform the
scan does not have the appropriate permissions to execute the command used to
gather the targeted facts. The following set of facts require *admin/root*
Expand Down
157 changes: 157 additions & 0 deletions doc/generate_python_docs.py
@@ -0,0 +1,157 @@
#!/bin/python
#
# Copyright (c) 2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.

"""Extract a list of facts from our documentation.
The documentation is formatted as reStructured Text. We want to
extract one specific list in a structured way so we can return it from
'rho facts list'.
We check that the entries are in alphabetical order and write the fact
list as a Python source file containing a dictionary. This lets us
only maintain a single copy of the fact list while not adding an
additional source of errors at runtime.
"""

import collections
import logging
import os.path
import sys

import docutils
import docutils.parsers.rst

COMMAND_SYNTAX_USAGE_RST = 'command_syntax_usage.rst'

# The description nodes in the tree all have this prefix
DESCRIPTION_PREFIX = ' - '


def get_fact_docs(doc_dir):
"""Return the fact list.
:param base_dir: path to the base of the rho source tree.
:returns: a collections.OrderedDict mapping fact names to
descriptions, sorted by fact name.
"""

# Find the documentation and read it
rst_path = os.path.join(doc_dir, COMMAND_SYNTAX_USAGE_RST)
raw_rst = open(rst_path, 'r').read()

# Parse it using docutils
parser = docutils.parsers.rst.Parser()
settings = docutils.frontend.OptionParser(
components=(docutils.parsers.rst.Parser,)).get_default_values()
document = docutils.utils.new_document(COMMAND_SYNTAX_USAGE_RST, settings)
parser.parse(raw_rst, document)

# Find the list we need
output = find_doc_elt_with_id(document, 'output')
fact_list = None
for elt in output.children:
if isinstance(elt, docutils.nodes.bullet_list):
fact_list = elt
break
if not fact_list:
raise ValueError("Couldn't find the fact list in the documentation.")

# Turn it into a dictionary
fact_docs = collections.OrderedDict()
for elt in fact_list:
logging.debug('extracting data from %s', elt)
assert isinstance(elt, docutils.nodes.list_item)
assert len(elt) == 1
assert isinstance(elt[0], docutils.nodes.paragraph)

# The paragraph starts with a literal containing the fact
# name, then ' - ', and then a variable number of nodes
# containing the documentation.
fact_name = elt[0][0].astext()
paragraph = elt[0].astext()

prefix = fact_name + DESCRIPTION_PREFIX

assert paragraph.startswith(prefix)
description = paragraph[len(prefix):]

fact_docs[fact_name] = description

return fact_docs


def find_doc_elt_with_id(root, elt_id):
"""Find a docutils tree element with a given id.
:param root: the root of the element tree to search.
:param elt_id: the ID of the element to find.
:returns: a docutils Element, or None if not found.
"""

logging.debug('visiting %s', repr(root))

if hasattr(root, 'get') and root.get('ids', None) == [elt_id]:
return root

for elt in root.children:
logging.debug('descending from %s', repr(root))

val = find_doc_elt_with_id(elt, elt_id)

if val:
return val

return None


def write_docs_as_python(fact_docs, out_file):
"""Write the fact docs in Python format.
Writes Python code to out_file such that if the code were loaded
as a Python module, the module would have a single element called
'FACT_DOCS' which would be a collections.OrderedDict with the same
contents as od.
:param od: a collections.OrderedDict mapping strings to strings.
"""

out_file.write("""# THIS FILE IS AUTOMATICALLY GENERATED.
# Do not edit this file. Instead, edit 'doc/generate_python_docs.py'
# to make the change you want to see, and run 'make docs' to generate
# a new version of this.
'''Documentation for individual Rho facts.'''
import collections
FACT_DOCS = collections.OrderedDict([
""")

for key, value in fact_docs.items():
out_file.write(' ({0}, {1}),\n'.format(repr(key), repr(value)))

out_file.write('])\n')


def main(argv):
"""Run the script."""

doc_dir = os.path.dirname(argv[0])

fact_docs = get_fact_docs(doc_dir)

with open('fact_docs.py', 'w') as out_file:
write_docs_as_python(fact_docs, out_file)


if __name__ == '__main__':
main(sys.argv)
1 change: 1 addition & 0 deletions rho/cli.py
Expand Up @@ -25,6 +25,7 @@
from rho.autheditcommand import AuthEditCommand # noqa
from rho.authlistcommand import AuthListCommand # noqa
from rho.authshowcommand import AuthShowCommand # noqa
from rho.factlistcommand import FactListCommand # noqa
from rho.profileaddcommand import ProfileAddCommand # noqa
from rho.profileclearcommand import ProfileClearCommand # noqa
from rho.profileeditcommand import ProfileEditCommand # noqa
Expand Down
72 changes: 72 additions & 0 deletions rho/fact_docs.py
@@ -0,0 +1,72 @@
# THIS FILE IS AUTOMATICALLY GENERATED.

# Do not edit this file. Instead, edit 'doc/generate_python_docs.py'
# to make the change you want to see, and run 'make docs' to generate
# a new version of this.

'''Documentation for individual Rho facts.'''

import collections

FACT_DOCS = collections.OrderedDict([
(u'connection.host', u'The host address of the connection'),
(u'connection.port', u'The port used for the connection'),
(u'connection.uuid', u'A generated identifier for the connection'),
(u'cpu.bogomips', u'measurement of CPU speed made by the Linux kernel'),
(u'cpu.count', u'number of processors'),
(u'cpu.cpu_family', u'cpu family'),
(u'cpu.model_name', u'cpu model name'),
(u'cpu.model_ver', u'cpu model version'),
(u'cpu.socket_count', u'number of sockets'),
(u'cpu.vendor_id', u'cpu vendor name'),
(u'date.anaconda_log', u'/root/anaconda-ks.cfg modified time'),
(u'date.date', u'date'),
(u'date.filesystem_create', u'uses tune2fs -l on the / filesystem dev found using mount'),
(u'date.machine_id', u"/etc/machine-id modified time'"),
(u'date.yum_history', u'dates from yum history'),
(u'dmi.bios-vendor', u'bios vendor name'),
(u'dmi.bios-version', u'bios version info'),
(u'dmi.processor-family', u'processor family'),
(u'dmi.system-manufacturer', u'system manufacturer'),
(u'etc-issue.etc-issue', u'contents of /etc/issue (or equivalent)'),
(u'etc-release.name', u'name of the release'),
(u'etc-release.release', u'release information'),
(u'etc-release.version', u'release version'),
(u'instnum.instnum', u'installation number'),
(u'jboss.brms.drools_core_ver', u'Drools version'),
(u'jboss.brms.kie_api_ver', u'KIE API version'),
(u'jboss.brms.kie_war_ver', u'KIE runtime version'),
(u'jboss.deploy_dates', u'List of deployment dates of JBoss installations'),
(u'jboss.fuse.activemq-ver', u'ActiveMQ version'),
(u'jboss.fuse.camel-ver', u'Camel version'),
(u'jboss.fuse.cxf-ver', u'CXF version'),
(u'jboss.installed_versions', u'List of installed versions of JBoss'),
(u'jboss.running_versions', u'List of running versions of JBoss'),
(u'redhat-packages.is_redhat', u'determines if package is a Red Hat package'),
(u'redhat-packages.last_installed', u'last installed package'),
(u'redhat-packages.last_built', u'last built package'),
(u'redhat-packages.num_rh_packages', u'number of Red Hat packages'),
(u'redhat-packages.num_installed_packages', u'number of installed packages'),
(u'redhat-release.name', u"name of package that provides 'redhat-release'"),
(u'redhat-release.release', u"release of package that provides 'redhat-release'"),
(u'redhat-release.version', u"version of package that provides 'redhat-release'"),
(u'subman.cpu.core(s)_per_socket', u'cpu cores per socket from subscription manager'),
(u'subman.cpu.cpu(s)', u'cpus from subscription manager'),
(u'subman.cpu.cpu_socket(s)', u'cpu sockets from subscription manager'),
(u'subman.virt.is_guest', u'Whether is a virtual guest from subscription manager'),
(u'subman.virt.host_type', u'Virtual host type from subscription manager'),
(u'subman.virt.uuid', u'Virtual host uuid from subscription manager'),
(u'systemid.system_id', u'Red Hat Network System ID'),
(u'systemid.username', u'Red Hat Network username'),
(u'uname.all', u'uname -a (all)'),
(u'uname.hardware_platform', u'uname -i (hardware_platform)'),
(u'uname.hostname', u'uname -n (hostname)'),
(u'uname.kernel', u'uname -r (kernel)'),
(u'uname.os', u'uname -s (os)'),
(u'uname.processor', u'uname -p (processor)'),
(u'virt.num_guests', u'the number of virtualized guests'),
(u'virt.num_running_guests', u'the number of running virtualized guests'),
(u'virt.type', u'type of virtual system'),
(u'virt.virt', u'host, guest, or baremetal'),
(u'virt-what.type', u'What type of virtualization a system is running'),
])
45 changes: 45 additions & 0 deletions rho/factlistcommand.py
@@ -0,0 +1,45 @@
# Copyright (c) 2017 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.

"""List available facts."""

from __future__ import print_function

import re

from rho import utilities
from rho.clicommand import CliCommand
from rho.fact_docs import FACT_DOCS
from rho.translation import _


# pylint: disable=too-few-public-methods
class FactListCommand(CliCommand):
"""List available facts."""

def __init__(self):
usage = _('usage: %prog fact list')
shortdesc = _('list facts that Rho can detect')
desc = _('list facts that Rho can detect. Filter fact names with '
'--filter <regex>')

CliCommand.__init__(self, 'fact list', usage, shortdesc, desc)

self.parser.add_option('--filter', dest='filter', metavar='filter',
help=_('regexp to filter facts - optional'))

def _do_command(self):
if self.options.filter:
regexp = re.compile(self.options.filter)
else:
regexp = None

for fact, desc in utilities.iteritems(FACT_DOCS):
if not regexp or regexp.match(fact):
print(fact, '-', desc)
12 changes: 12 additions & 0 deletions rho/utilities.py
Expand Up @@ -202,3 +202,15 @@ def str_to_ascii(in_string):
:returns: ASCII encoded string
"""
return in_string.decode('utf-8').encode('ascii')


def iteritems(dictionary):
"""Iterate over a dictionary's (key, value) pairs using Python 2 or 3.
:param dictionary: the dictionary to iterate over.
"""

if sys.version_info.major == 2:
return dictionary.iteritems()

return dictionary.items()

0 comments on commit 2c1d5e3

Please sign in to comment.