Sometimes it can be difficult to determine the access permissions needed
for a certain RAPI resource without looking at code. This table, added
at the end of “rapi.rst”, shows all resources and the permissions needed
for their methods.
Another nice side-effect of this change is that there's an automatic
cross-checking between implemented resources and methods and the
documentation.
---
doc/rapi.rst | 10 ++++
lib/build/sphinx_ext.py | 118 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 128 insertions(+)
diff --git a/doc/rapi.rst b/doc/rapi.rst
index 23f827e..209945b 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -2340,6 +2340,16 @@ It supports the following commands: ``GET``.
Returns the remote API version. Ganeti 1.2 returned ``1`` and Ganeti 2.0
returns ``2``.
+
+Access permissions
+------------------
+
+The following list describes the access permissions required for each
+resource. See :ref:`rapi-users` for more details.
+
+.. rapi_access_table::
+
+
.. vim: set textwidth=72 :
.. Local Variables:
.. mode: rst
diff --git a/lib/build/sphinx_ext.py b/lib/build/sphinx_ext.py
index 092e3cf..75f5a6e 100644
--- a/lib/build/sphinx_ext.py
+++ b/lib/build/sphinx_ext.py
@@ -58,6 +58,7 @@ from ganeti import objects
from ganeti import _autoconf
import ganeti.rapi.rlib2 # pylint: disable=W0611
+import ganeti.rapi.connector # pylint: disable=W0611
#: Regular expression for man page names
@@ -65,6 +66,15 @@ _MAN_RE =
re.compile(r"^(?P<name>[-\w_]+)\((?P<section>\d+)\)$")
_TAB_WIDTH = 2
+RAPI_URI_ENCODE_RE = re.compile("[^_a-z0-9]+", re.I)
+
+RAPI_ACCESS_TEXT = {
+ rapi.RAPI_ACCESS_WRITE: "write",
+ rapi.RAPI_ACCESS_READ: "read",
+ }
+
+assert frozenset(RAPI_ACCESS_TEXT.keys()) == rapi.RAPI_ACCESS_ALL
+
class ReSTError(Exception):
"""Custom class for generating errors in Sphinx.
@@ -425,6 +435,113 @@ def _ManPageRole(typ, rawtext, text, lineno, inliner, #
pylint: disable=W0102
options=options, content=content)
+def _EncodeRapiResourceLink(method, uri):
+ """Encodes a RAPI resource URI for use as a link target.
+
+ """
+ parts = [RAPI_URI_ENCODE_RE.sub("-", uri.lower()).strip("-")]
+
+ if method is not None:
+ parts.append(method.lower())
+
+ return "rapi-res-%s" % "+".join(filter(None, parts))
+
+
+def _MakeRapiResourceLink(method, uri):
+ """Generates link target name for RAPI resource.
+
+ """
+ if uri in ["/", "/2"]:
+ # Don't link these
+ return None
+
+ elif uri == "/version":
+ return _EncodeRapiResourceLink(method, uri)
+
+ elif uri.startswith("/2/"):
+ return _EncodeRapiResourceLink(method, uri[len("/2/"):])
+
+ else:
+ raise ReSTError("Unhandled URI '%s'" % uri)
+
+
+def _BuildRapiAccessTable(res):
+ """Build a table with access permissions needed for all RAPI resources.
+
+ """
+ for (uri, handler) in utils.NiceSort(res.items(), key=compat.fst):
+ reslink = _MakeRapiResourceLink(None, uri)
+ if not reslink:
+ # No link was generated
+ continue
+
+ yield ":ref:`%s <%s>`" % (uri, reslink)
+
+ for (method, op_attr, _, _) in sorted(rapi.baserlib.OPCODE_ATTRS):
+ if not (hasattr(handler, method) or hasattr(handler, op_attr)):
+ # Handler doesn't support method
+ continue
+
+ access = rapi.baserlib.GetHandlerAccess(handler, method)
+
+ perms = map(RAPI_ACCESS_TEXT.__getitem__, access)
+
+ if not perms:
+ perms.append("*everyone*")
+
+ yield (" | :ref:`%s <%s>`: %s" %
+ (method, _MakeRapiResourceLink(method, uri),
+ utils.CommaJoin(perms)))
+
+
+class RapiAccessTable(s_compat.Directive):
+ """Custom directive to generate table of all RAPI resources.
+
+ See also <http://docutils.sourceforge.net/docs/howto/rst-directives.html>.
+
+ """
+ has_content = False
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {}
+
+ def run(self):
+ resources = \
+ rapi.connector.GetHandlers("[node_name]", "[instance_name]",
+ "[group_name]", "[network_name]", "[job_id]", "[disk_index]",
+ "[resource]",
+ translate=self._TranslateResourceUri)
+
+ include_text = "\n".join(_BuildRapiAccessTable(resources))
+
+ # Inject into state machine
+ include_lines = docutils.statemachine.string2lines(include_text,
_TAB_WIDTH,
+ convert_whitespace=1)
+ self.state_machine.insert_input(include_lines, self.__class__.__name__)
+
+ return []
+
+ @classmethod
+ def _TranslateResourceUri(cls, *args):
+ """Translates a resource URI for use in documentation.
+
+ @see: L{rapi.connector.GetHandlers}
+
+ """
+ return "".join(map(cls._UriPatternToString, args))
+
+ @staticmethod
+ def _UriPatternToString(value):
+ """Converts L{rapi.connector.UriPattern} to strings.
+
+ """
+ if isinstance(value, rapi.connector.UriPattern):
+ return value.content
+ else:
+ return value
+
+
def setup(app):
"""Sphinx extension callback.
@@ -434,6 +551,7 @@ def setup(app):
app.add_directive("opcode_result", OpcodeResult)
app.add_directive("pyassert", PythonAssert)
app.add_role("pyeval", PythonEvalRole)
+ app.add_directive("rapi_access_table", RapiAccessTable)
app.add_config_value("enable_manpages", False, True)
app.add_role("manpage", _ManPageRole)
--
1.8.1