mirror of
https://github.com/ceph/ceph
synced 2025-04-01 00:26:47 +00:00
doc: build mon_command_api.rst using a homebrew extension
so we can build the command doc using ReadTheDoc infra, without adding the generated rst file to the source repo. before this change, all the commands are ordered alphabetically. after this change, command docs are generated by two directives, and are ordered separately. we could restructure the directives and merge them. but let's leave it for a future change if this is important. for more details on writing sphinx directives, see https://www.sphinx-doc.org/en/master/extdev/markupapi.html and https://docutils.sourceforge.io/docs/howto/rst-directives.html Signed-off-by: Kefu Chai <kchai@redhat.com>
This commit is contained in:
parent
b84ff2b744
commit
202b805aaf
@ -64,11 +64,6 @@ install -d -m0755 \
|
||||
$TOPDIR/build-doc/output/html \
|
||||
$TOPDIR/build-doc/output/man
|
||||
|
||||
# required by script/gen_static_command_descriptions.py, which imports ceph_argparse
|
||||
export PYTHONPATH=$TOPDIR/src/pybind
|
||||
|
||||
$vdir/bin/python $TOPDIR/doc/scripts/gen_mon_command_api.py > $TOPDIR/doc/api/mon_command_api.rst
|
||||
|
||||
for opt in "$@"; do
|
||||
case $opt in
|
||||
html|man|livehtml)
|
||||
|
@ -1,3 +1 @@
|
||||
pcpp
|
||||
Jinja2
|
||||
src/python-common
|
||||
|
@ -1,8 +1,10 @@
|
||||
Sphinx == 3.2.1
|
||||
git+https://github.com/ceph/sphinx-ditaa.git@py3#egg=sphinx-ditaa
|
||||
breathe >= 4.20.0
|
||||
Jinja2
|
||||
pyyaml >= 5.1.2
|
||||
Cython
|
||||
pcpp
|
||||
prettytable
|
||||
sphinx-autodoc-typehints
|
||||
sphinx-prompt
|
||||
|
1
doc/.gitignore
vendored
1
doc/.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
/overview.png
|
||||
/object_store.png
|
||||
/api/mon_command_api.rst
|
429
doc/_ext/ceph_commands.py
Normal file
429
doc/_ext/ceph_commands.py
Normal file
@ -0,0 +1,429 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import contextlib
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
from docutils.parsers.rst import Directive
|
||||
from jinja2 import Template
|
||||
from pcpp.preprocessor import Preprocessor
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.console import bold
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Flags:
|
||||
NOFORWARD = (1 << 0)
|
||||
OBSOLETE = (1 << 1)
|
||||
DEPRECATED = (1 << 2)
|
||||
MGR = (1 << 3)
|
||||
POLL = (1 << 4)
|
||||
HIDDEN = (1 << 5)
|
||||
|
||||
VALS = {
|
||||
NOFORWARD: 'no_forward',
|
||||
OBSOLETE: 'obsolete',
|
||||
DEPRECATED: 'deprecated',
|
||||
MGR: 'mgr',
|
||||
POLL: 'poll',
|
||||
HIDDEN: 'hidden',
|
||||
}
|
||||
|
||||
def __init__(self, fs):
|
||||
self.fs = fs
|
||||
|
||||
def __contains__(self, other):
|
||||
return other in str(self)
|
||||
|
||||
def __str__(self):
|
||||
keys = Flags.VALS.keys()
|
||||
es = {Flags.VALS[k] for k in keys if self.fs & k == k}
|
||||
return ', '.join(sorted(es))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(str(self))
|
||||
|
||||
|
||||
class CmdParam(object):
|
||||
t = {
|
||||
'CephInt': 'int',
|
||||
'CephString': 'str',
|
||||
'CephChoices': 'str',
|
||||
'CephPgid': 'str',
|
||||
'CephOsdName': 'str',
|
||||
'CephPoolname': 'str',
|
||||
'CephObjectname': 'str',
|
||||
'CephUUID': 'str',
|
||||
'CephEntityAddr': 'str',
|
||||
'CephIPAddr': 'str',
|
||||
'CephName': 'str',
|
||||
'CephBool': 'bool',
|
||||
'CephFloat': 'float',
|
||||
'CephFilepath': 'str',
|
||||
}
|
||||
|
||||
bash_example = {
|
||||
'CephInt': '1',
|
||||
'CephString': 'string',
|
||||
'CephChoices': 'choice',
|
||||
'CephPgid': '0',
|
||||
'CephOsdName': 'osd.0',
|
||||
'CephPoolname': 'poolname',
|
||||
'CephObjectname': 'objectname',
|
||||
'CephUUID': 'uuid',
|
||||
'CephEntityAddr': 'entityaddr',
|
||||
'CephIPAddr': '0.0.0.0',
|
||||
'CephName': 'name',
|
||||
'CephBool': 'true',
|
||||
'CephFloat': '0.0',
|
||||
'CephFilepath': '/path/to/file',
|
||||
}
|
||||
|
||||
def __init__(self, type, name, who=None, n=None, req=True, range=None, strings=None,
|
||||
goodchars=None):
|
||||
self.type = type
|
||||
self.name = name
|
||||
self.who = who
|
||||
self.n = n == 'N'
|
||||
self.req = req != 'false'
|
||||
self.range = range.split('|') if range else []
|
||||
self.strings = strings.split('|') if strings else []
|
||||
self.goodchars = goodchars
|
||||
|
||||
assert who == None
|
||||
|
||||
def help(self):
|
||||
advanced = []
|
||||
if self.type != 'CephString':
|
||||
advanced.append(self.type + ' ')
|
||||
if self.range:
|
||||
advanced.append('range= ``{}`` '.format('..'.join(self.range)))
|
||||
if self.strings:
|
||||
advanced.append('strings=({}) '.format(' '.join(self.strings)))
|
||||
if self.goodchars:
|
||||
advanced.append('goodchars= ``{}`` '.format(self.goodchars))
|
||||
if self.n:
|
||||
advanced.append('(can be repeated)')
|
||||
|
||||
advanced = advanced or ["(string)"]
|
||||
return ' '.join(advanced)
|
||||
|
||||
def mk_example_value(self):
|
||||
if self.type == 'CephChoices' and self.strings:
|
||||
return self.strings[0]
|
||||
if self.range:
|
||||
return self.range[0]
|
||||
return CmdParam.bash_example[self.type]
|
||||
|
||||
def mk_bash_example(self, simple):
|
||||
val = self.mk_example_value()
|
||||
|
||||
if self.type == 'CephBool':
|
||||
return '--' + self.name
|
||||
if simple:
|
||||
if self.type == "CephChoices" and self.strings:
|
||||
return val
|
||||
elif self.type == "CephString" and self.name != 'who':
|
||||
return 'my_' + self.name
|
||||
else:
|
||||
return CmdParam.bash_example[self.type]
|
||||
else:
|
||||
return '--{}={}'.format(self.name, val)
|
||||
|
||||
|
||||
class CmdCommand(object):
|
||||
def __init__(self, sig, desc, module=None, perm=None, flags=0, poll=None):
|
||||
self.sig = [s for s in sig if isinstance(s, str)]
|
||||
self.params = sorted([CmdParam(**s) for s in sig if not isinstance(s, str)],
|
||||
key=lambda p: p.req, reverse=True)
|
||||
self.help = desc
|
||||
self.module = module
|
||||
self.perm = perm
|
||||
self.flags = Flags(flags)
|
||||
self.needs_overload = False
|
||||
|
||||
def prefix(self):
|
||||
return ' '.join(self.sig)
|
||||
|
||||
def is_reasonably_simple(self):
|
||||
if len(self.params) > 3:
|
||||
return False
|
||||
if any(p.n for p in self.params):
|
||||
return False
|
||||
return True
|
||||
|
||||
def mk_bash_example(self):
|
||||
simple = self.is_reasonably_simple()
|
||||
line = ' '.join(['ceph', self.prefix()] + [p.mk_bash_example(simple) for p in self.params])
|
||||
return line
|
||||
|
||||
|
||||
class Sig:
|
||||
@staticmethod
|
||||
def _param_to_sig(p):
|
||||
try:
|
||||
return {kv.split('=')[0]: kv.split('=')[1] for kv in p.split(',')}
|
||||
except IndexError:
|
||||
return p
|
||||
|
||||
@staticmethod
|
||||
def from_cmd(cmd):
|
||||
sig = cmd.split()
|
||||
return [Sig._param_to_sig(s) or s for s in sig]
|
||||
|
||||
|
||||
TEMPLATE = '''
|
||||
.. This file is automatically generated. do not modify
|
||||
|
||||
{% for command in commands %}
|
||||
|
||||
{{ command.prefix() }}
|
||||
{{ command.prefix() | length * '^' }}
|
||||
|
||||
{{ command.help | wordwrap(70)}}
|
||||
|
||||
Example command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
{{ command.mk_bash_example() }}
|
||||
{% if command.params %}
|
||||
Parameters:
|
||||
|
||||
{% for param in command.params %}* **{{param.name}}**: {{ param.help() | wordwrap(70) | indent(2) }}
|
||||
{% endfor %}{% endif %}
|
||||
Ceph Module:
|
||||
|
||||
* *{{ command.module }}*
|
||||
|
||||
Required Permissions:
|
||||
|
||||
* *{{ command.perm }}*
|
||||
|
||||
{% if command.flags %}Command Flags:
|
||||
|
||||
* *{{ command.flags }}*
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
'''
|
||||
|
||||
class CephMgrCommands(Directive):
|
||||
"""
|
||||
extracts commands from specified mgr modules
|
||||
"""
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
|
||||
def _normalize_path(self, dirname):
|
||||
my_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
src_dir = os.path.abspath(os.path.join(my_dir, '../..'))
|
||||
return os.path.join(src_dir, dirname)
|
||||
|
||||
def _is_mgr_module(self, dirname, name):
|
||||
if not os.path.isdir(os.path.join(dirname, name)):
|
||||
return False
|
||||
if not os.path.isfile(os.path.join(dirname, name, '__init__.py')):
|
||||
return False
|
||||
return name not in ['tests']
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mocked_modules(self):
|
||||
# src/pybind/mgr/tests
|
||||
from tests import mock
|
||||
mock_imports = ['rados',
|
||||
'rbd',
|
||||
'cephfs',
|
||||
'dateutil',
|
||||
'dateutil.parser']
|
||||
# make dashboard happy
|
||||
mock_imports += ['ceph_argparse',
|
||||
'OpenSSL',
|
||||
'jwt',
|
||||
'bcrypt',
|
||||
'scipy',
|
||||
'jsonpatch',
|
||||
'rook.rook_client',
|
||||
'rook.rook_client.ceph',
|
||||
'cherrypy=3.2.3']
|
||||
|
||||
# make restful happy
|
||||
mock_imports += ['pecan',
|
||||
'pecan.rest',
|
||||
'pecan.hooks',
|
||||
'werkzeug',
|
||||
'werkzeug.serving']
|
||||
|
||||
for m in mock_imports:
|
||||
args = {}
|
||||
parts = m.split('=', 1)
|
||||
mocked = parts[0]
|
||||
if len(parts) > 1:
|
||||
args['__version__'] = parts[1]
|
||||
sys.modules[mocked] = mock.Mock(**args)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for m in mock_imports:
|
||||
mocked = m.split('=', 1)[0]
|
||||
sys.modules.pop(mocked)
|
||||
|
||||
def _collect_module_commands(self, name):
|
||||
with self.mocked_modules():
|
||||
logger.info(bold(f"loading mgr module '{name}'..."))
|
||||
mgr_mod = __import__(name, globals(), locals(), [], 0)
|
||||
from tests import M
|
||||
def subclass(x):
|
||||
try:
|
||||
return issubclass(x, M)
|
||||
except TypeError:
|
||||
return False
|
||||
ms = [c for c in mgr_mod.__dict__.values()
|
||||
if subclass(c) and 'Standby' not in c.__name__]
|
||||
[m] = ms
|
||||
assert isinstance(m.COMMANDS, list)
|
||||
return m.COMMANDS
|
||||
|
||||
def _normalize_command(self, command):
|
||||
if 'handler' in command:
|
||||
del command['handler']
|
||||
command['sig'] = Sig.from_cmd(command['cmd'])
|
||||
del command['cmd']
|
||||
command['flags'] = (1 << 3)
|
||||
command['module'] = 'mgr'
|
||||
return command
|
||||
|
||||
def _render_cmds(self, commands):
|
||||
rendered = Template(TEMPLATE).render(commands=list(commands))
|
||||
lines = rendered.split("\n")
|
||||
assert lines
|
||||
source = self.state_machine.input_lines.source(self.lineno -
|
||||
self.state_machine.input_offset - 1)
|
||||
self.state_machine.insert_input(lines, source)
|
||||
|
||||
def run(self):
|
||||
module_path = self._normalize_path(self.arguments[0])
|
||||
sys.path.insert(0, module_path)
|
||||
os.environ['UNITTEST'] = 'true'
|
||||
modules = [name for name in os.listdir(module_path)
|
||||
if self._is_mgr_module(module_path, name)]
|
||||
commands = sum([self._collect_module_commands(name) for name in modules], [])
|
||||
cmds = [CmdCommand(**self._normalize_command(c)) for c in commands]
|
||||
cmds = [cmd for cmd in cmds if 'hidden' not in cmd.flags]
|
||||
cmds = sorted(cmds, key=lambda cmd: cmd.sig)
|
||||
self._render_cmds(cmds)
|
||||
return []
|
||||
|
||||
class MyProcessor(Preprocessor):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.cmds = []
|
||||
self.undef('__DATE__')
|
||||
self.undef('__TIME__')
|
||||
self.expand_linemacro = False
|
||||
self.expand_filemacro = False
|
||||
self.expand_countermacro = False
|
||||
self.line_directive = '#line'
|
||||
self.define("__PCPP_VERSION__ " + '')
|
||||
self.define("__PCPP_ALWAYS_FALSE__ 0")
|
||||
self.define("__PCPP_ALWAYS_TRUE__ 1")
|
||||
|
||||
def eval(self, src):
|
||||
_cmds = []
|
||||
|
||||
NONE = 0
|
||||
NOFORWARD = (1 << 0)
|
||||
OBSOLETE = (1 << 1)
|
||||
DEPRECATED = (1 << 2)
|
||||
MGR = (1 << 3)
|
||||
POLL = (1 << 4)
|
||||
HIDDEN = (1 << 5)
|
||||
TELL = (1 << 6)
|
||||
|
||||
def FLAG(a):
|
||||
return a
|
||||
|
||||
def COMMAND(cmd, desc, module, perm):
|
||||
_cmds.append({
|
||||
'cmd': cmd,
|
||||
'desc': desc,
|
||||
'module': module,
|
||||
'perm': perm
|
||||
})
|
||||
|
||||
def COMMAND_WITH_FLAG(cmd, desc, module, perm, flag):
|
||||
_cmds.append({
|
||||
'cmd': cmd,
|
||||
'desc': desc,
|
||||
'module': module,
|
||||
'perm': perm,
|
||||
'flags': flag
|
||||
})
|
||||
|
||||
self.parse(src)
|
||||
out = io.StringIO()
|
||||
self.write(out)
|
||||
out.seek(0)
|
||||
s = out.read()
|
||||
exec(s, globals(), locals())
|
||||
return _cmds
|
||||
|
||||
|
||||
class CephMonCommands(Directive):
|
||||
"""
|
||||
extracts commands from specified header file
|
||||
"""
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
|
||||
|
||||
def _src_dir(self):
|
||||
my_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
return os.path.abspath(os.path.join(my_dir, '../..'))
|
||||
|
||||
def _parse_headers(self, headers):
|
||||
src_dir = self._src_dir()
|
||||
src = '\n'.join(f'#include "{src_dir}/{header}"' for header in headers)
|
||||
return MyProcessor().eval(src)
|
||||
|
||||
def _normalize_command(self, command):
|
||||
if 'handler' in command:
|
||||
del command['handler']
|
||||
command['sig'] = Sig.from_cmd(command['cmd'])
|
||||
del command['cmd']
|
||||
return command
|
||||
|
||||
def _render_cmds(self, commands):
|
||||
rendered = Template(TEMPLATE).render(commands=list(commands))
|
||||
lines = rendered.split("\n")
|
||||
assert lines
|
||||
source = self.state_machine.input_lines.source(self.lineno -
|
||||
self.state_machine.input_offset - 1)
|
||||
self.state_machine.insert_input(lines, source)
|
||||
|
||||
def run(self):
|
||||
headers = self.arguments[0].split()
|
||||
commands = self._parse_headers(headers)
|
||||
cmds = [CmdCommand(**self._normalize_command(c)) for c in commands]
|
||||
cmds = [cmd for cmd in cmds if 'hidden' not in cmd.flags]
|
||||
cmds = sorted(cmds, key=lambda cmd: cmd.sig)
|
||||
self._render_cmds(cmds)
|
||||
return []
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive("ceph-mgr-commands", CephMgrCommands)
|
||||
app.add_directive("ceph-mon-commands", CephMonCommands)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
2
doc/api/mon_command_api.rst
Normal file
2
doc/api/mon_command_api.rst
Normal file
@ -0,0 +1,2 @@
|
||||
.. ceph-mgr-commands:: src/pybind/mgr
|
||||
.. ceph-mon-commands:: src/mon/MonCommands.h src/mgr/MgrCommands.h
|
@ -102,6 +102,7 @@ extensions = [
|
||||
'sphinx_autodoc_typehints',
|
||||
'sphinx_substitution_extensions',
|
||||
'breathe',
|
||||
'ceph_commands',
|
||||
'ceph_releases',
|
||||
'sphinxcontrib.openapi'
|
||||
]
|
||||
|
@ -1,206 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from subprocess import check_output
|
||||
|
||||
from jinja2 import Template
|
||||
|
||||
|
||||
class Flags:
|
||||
NOFORWARD = (1 << 0)
|
||||
OBSOLETE = (1 << 1)
|
||||
DEPRECATED = (1 << 2)
|
||||
MGR = (1 << 3)
|
||||
POLL = (1 << 4)
|
||||
HIDDEN = (1 << 5)
|
||||
|
||||
VALS = {
|
||||
NOFORWARD: 'no_forward',
|
||||
OBSOLETE: 'obsolete',
|
||||
DEPRECATED: 'deprecated',
|
||||
MGR: 'mgr',
|
||||
POLL: 'poll',
|
||||
HIDDEN: 'hidden',
|
||||
}
|
||||
|
||||
def __init__(self, fs):
|
||||
self.fs = fs
|
||||
|
||||
def __contains__(self, other):
|
||||
return other in str(self)
|
||||
|
||||
def __str__(self):
|
||||
keys = Flags.VALS.keys()
|
||||
es = {Flags.VALS[k] for k in keys if self.fs & k == k}
|
||||
return ', '.join(sorted(es))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(str(self))
|
||||
|
||||
|
||||
class CmdParam(object):
|
||||
t = {
|
||||
'CephInt': 'int',
|
||||
'CephString': 'str',
|
||||
'CephChoices': 'str',
|
||||
'CephPgid': 'str',
|
||||
'CephOsdName': 'str',
|
||||
'CephPoolname': 'str',
|
||||
'CephObjectname': 'str',
|
||||
'CephUUID': 'str',
|
||||
'CephEntityAddr': 'str',
|
||||
'CephIPAddr': 'str',
|
||||
'CephName': 'str',
|
||||
'CephBool': 'bool',
|
||||
'CephFloat': 'float',
|
||||
'CephFilepath': 'str',
|
||||
}
|
||||
|
||||
bash_example = {
|
||||
'CephInt': '1',
|
||||
'CephString': 'string',
|
||||
'CephChoices': 'choice',
|
||||
'CephPgid': '0',
|
||||
'CephOsdName': 'osd.0',
|
||||
'CephPoolname': 'poolname',
|
||||
'CephObjectname': 'objectname',
|
||||
'CephUUID': 'uuid',
|
||||
'CephEntityAddr': 'entityaddr',
|
||||
'CephIPAddr': '0.0.0.0',
|
||||
'CephName': 'name',
|
||||
'CephBool': 'true',
|
||||
'CephFloat': '0.0',
|
||||
'CephFilepath': '/path/to/file',
|
||||
}
|
||||
|
||||
def __init__(self, type, name, who=None, n=None, req=True, range=None, strings=None,
|
||||
goodchars=None):
|
||||
self.type = type
|
||||
self.name = name
|
||||
self.who = who
|
||||
self.n = n == 'N'
|
||||
self.req = req != 'false'
|
||||
self.range = range.split('|') if range else []
|
||||
self.strings = strings.split('|') if strings else []
|
||||
self.goodchars = goodchars
|
||||
|
||||
assert who == None
|
||||
|
||||
def help(self):
|
||||
advanced = []
|
||||
if self.type != 'CephString':
|
||||
advanced.append(self.type + ' ')
|
||||
if self.range:
|
||||
advanced.append('range= ``{}`` '.format('..'.join(self.range)))
|
||||
if self.strings:
|
||||
advanced.append('strings=({}) '.format(' '.join(self.strings)))
|
||||
if self.goodchars:
|
||||
advanced.append('goodchars= ``{}`` '.format(self.goodchars))
|
||||
if self.n:
|
||||
advanced.append('(can be repeated)')
|
||||
|
||||
advanced = advanced or ["(string)"]
|
||||
return ' '.join(advanced)
|
||||
|
||||
def mk_example_value(self):
|
||||
if self.type == 'CephChoices' and self.strings:
|
||||
return self.strings[0]
|
||||
if self.range:
|
||||
return self.range[0]
|
||||
return CmdParam.bash_example[self.type]
|
||||
|
||||
def mk_bash_example(self, simple):
|
||||
val = self.mk_example_value()
|
||||
|
||||
if self.type == 'CephBool':
|
||||
return '--' + self.name
|
||||
if simple:
|
||||
if self.type == "CephChoices" and self.strings:
|
||||
return val
|
||||
elif self.type == "CephString" and self.name != 'who':
|
||||
return 'my_' + self.name
|
||||
else:
|
||||
return CmdParam.bash_example[self.type]
|
||||
else:
|
||||
return '--{}={}'.format(self.name, val)
|
||||
|
||||
|
||||
class CmdCommand(object):
|
||||
def __init__(self, sig, desc, module=None, perm=None, flags=0, poll=None):
|
||||
self.sig = [s for s in sig if isinstance(s, str)]
|
||||
self.params = sorted([CmdParam(**s) for s in sig if not isinstance(s, str)],
|
||||
key=lambda p: p.req, reverse=True)
|
||||
self.help = desc
|
||||
self.module = module
|
||||
self.perm = perm
|
||||
self.flags = Flags(flags)
|
||||
self.needs_overload = False
|
||||
|
||||
def prefix(self):
|
||||
return ' '.join(self.sig)
|
||||
|
||||
def is_reasonably_simple(self):
|
||||
if len(self.params) > 3:
|
||||
return False
|
||||
if any(p.n for p in self.params):
|
||||
return False
|
||||
return True
|
||||
|
||||
def mk_bash_example(self):
|
||||
simple = self.is_reasonably_simple()
|
||||
line = ' '.join(['ceph', self.prefix()] + [p.mk_bash_example(simple) for p in self.params])
|
||||
return line
|
||||
|
||||
|
||||
tpl = '''
|
||||
.. This file is automatically generated. do not modify
|
||||
|
||||
{% for command in commands %}
|
||||
|
||||
{{ command.prefix() }}
|
||||
{{ command.prefix() | length * '^' }}
|
||||
|
||||
{{ command.help | wordwrap(70)}}
|
||||
|
||||
Example command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
{{ command.mk_bash_example() }}
|
||||
{% if command.params %}
|
||||
Parameters:
|
||||
|
||||
{% for param in command.params %}* **{{param.name}}**: {{ param.help() | wordwrap(70) | indent(2) }}
|
||||
{% endfor %}{% endif %}
|
||||
Ceph Module:
|
||||
|
||||
* *{{ command.module }}*
|
||||
|
||||
Required Permissions:
|
||||
|
||||
* *{{ command.perm }}*
|
||||
|
||||
{% if command.flags %}Command Flags:
|
||||
|
||||
* *{{ command.flags }}*
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
'''
|
||||
|
||||
def mk_sigs(all):
|
||||
sigs = [CmdCommand(**e) for e in all]
|
||||
sigs = [s for s in sigs if 'hidden' not in s.flags]
|
||||
sigs = sorted(sigs, key=lambda f: f.sig)
|
||||
|
||||
|
||||
tm = Template(tpl)
|
||||
msg = tm.render(commands=list(sigs))
|
||||
|
||||
print(msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
commands = json.loads(check_output([sys.executable, script_dir + '/../../src/script/gen_static_command_descriptions.py']))
|
||||
mk_sigs(commands)
|
@ -1,169 +0,0 @@
|
||||
"""
|
||||
Prints a statically compiled list of all commands.
|
||||
|
||||
See
|
||||
|
||||
* /admin/doc-requirements.txt
|
||||
* /doc/scripts/gen_mon_command_api.py
|
||||
|
||||
Rational for putting this file here is to allow others to make use of this output.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import io
|
||||
import sys
|
||||
|
||||
from pcpp.preprocessor import Preprocessor, OutputDirective, Action
|
||||
|
||||
os.environ['UNITTEST'] = 'true'
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
mgr_dir = os.path.abspath(script_dir + '/../../src/pybind/mgr')
|
||||
|
||||
sys.path.insert(0, mgr_dir)
|
||||
|
||||
from tests import mock, M
|
||||
|
||||
|
||||
def param_to_sig(p):
|
||||
try:
|
||||
return {kv.split('=')[0]: kv.split('=')[1] for kv in p.split(',')}
|
||||
except IndexError:
|
||||
return p
|
||||
|
||||
def cmd_to_sig(cmd):
|
||||
sig = cmd.split()
|
||||
return [param_to_sig(s) or s for s in sig]
|
||||
|
||||
|
||||
def list_mgr_module(m_name):
|
||||
sys.modules['rados'] = mock.Mock()
|
||||
sys.modules['rbd'] = mock.Mock()
|
||||
sys.modules['cephfs'] = mock.Mock()
|
||||
sys.modules['dateutil'] = mock.Mock()
|
||||
sys.modules['dateutil.parser'] = mock.Mock()
|
||||
|
||||
# make dashboard happy
|
||||
sys.modules['OpenSSL'] = mock.Mock()
|
||||
sys.modules['jwt'] = mock.Mock()
|
||||
sys.modules['bcrypt'] = mock.Mock()
|
||||
|
||||
sys.modules['scipy'] = mock.Mock()
|
||||
sys.modules['jsonpatch'] = mock.Mock()
|
||||
sys.modules['rook.rook_client'] = mock.Mock()
|
||||
sys.modules['rook.rook_client.ceph'] = mock.Mock()
|
||||
|
||||
sys.modules['cherrypy'] = mock.Mock(__version__="3.2.3")
|
||||
|
||||
# make restful happy:
|
||||
sys.modules['pecan'] = mock.Mock()
|
||||
sys.modules['pecan.rest'] = mock.Mock()
|
||||
sys.modules['pecan.hooks'] = mock.Mock()
|
||||
sys.modules['werkzeug'] = mock.Mock()
|
||||
sys.modules['werkzeug.serving'] = mock.Mock()
|
||||
|
||||
mgr_mod = __import__(m_name, globals(), locals(), [], 0)
|
||||
|
||||
def subclass(x):
|
||||
try:
|
||||
return issubclass(x, M)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
ms = [c for c in mgr_mod.__dict__.values() if subclass(c) and 'Standby' not in c.__name__]
|
||||
[m] = ms
|
||||
assert isinstance(m.COMMANDS, list)
|
||||
return m.COMMANDS
|
||||
|
||||
|
||||
def from_mgr_modules():
|
||||
names = [name for name in os.listdir(mgr_dir)
|
||||
if os.path.isdir(os.path.join(mgr_dir, name)) and
|
||||
os.path.isfile(os.path.join(mgr_dir, name, '__init__.py')) and
|
||||
name not in ['tests']]
|
||||
|
||||
comms = sum([list_mgr_module(name) for name in names], [])
|
||||
for c in comms:
|
||||
if 'handler' in c:
|
||||
del c['handler']
|
||||
c['sig'] = cmd_to_sig(c['cmd'])
|
||||
del c['cmd']
|
||||
c['flags'] = (1 << 3)
|
||||
c['module'] = 'mgr'
|
||||
return comms
|
||||
|
||||
|
||||
def from_mon_commands_h():
|
||||
input_str = """
|
||||
#include "{script_dir}/../mon/MonCommands.h"
|
||||
#include "{script_dir}/../mgr/MgrCommands.h"
|
||||
""".format(script_dir=script_dir)
|
||||
|
||||
cmds = []
|
||||
|
||||
class MyProcessor(Preprocessor):
|
||||
def __init__(self):
|
||||
super(MyProcessor, self).__init__()
|
||||
self.undef('__DATE__')
|
||||
self.undef('__TIME__')
|
||||
self.expand_linemacro = False
|
||||
self.expand_filemacro = False
|
||||
self.expand_countermacro = False
|
||||
self.line_directive = '#line'
|
||||
self.define("__PCPP_VERSION__ " + '')
|
||||
self.define("__PCPP_ALWAYS_FALSE__ 0")
|
||||
self.define("__PCPP_ALWAYS_TRUE__ 1")
|
||||
self.parse(input_str)
|
||||
out = io.StringIO()
|
||||
self.write(out)
|
||||
out.seek(0)
|
||||
s = out.read()
|
||||
|
||||
NONE = 0
|
||||
NOFORWARD = (1 << 0)
|
||||
OBSOLETE = (1 << 1)
|
||||
DEPRECATED = (1 << 2)
|
||||
MGR = (1 << 3)
|
||||
POLL = (1 << 4)
|
||||
HIDDEN = (1 << 5)
|
||||
TELL = (1 << 6)
|
||||
|
||||
def FLAG(a):
|
||||
return a
|
||||
|
||||
def COMMAND(cmd, desc, module, perm):
|
||||
cmds.append({
|
||||
'cmd': cmd,
|
||||
'desc': desc,
|
||||
'module': module,
|
||||
'perm': perm
|
||||
})
|
||||
|
||||
def COMMAND_WITH_FLAG(cmd, desc, module, perm, flag):
|
||||
cmds.append({
|
||||
'cmd': cmd,
|
||||
'desc': desc,
|
||||
'module': module,
|
||||
'perm': perm,
|
||||
'flags': flag
|
||||
})
|
||||
|
||||
exec(s, globals(), locals())
|
||||
|
||||
MyProcessor()
|
||||
for c in cmds:
|
||||
if 'handler' in c:
|
||||
del c['handler']
|
||||
c['sig'] = cmd_to_sig(c['cmd'])
|
||||
del c['cmd']
|
||||
return cmds
|
||||
|
||||
|
||||
def gen_commands_dicts():
|
||||
comms = from_mon_commands_h() + from_mgr_modules()
|
||||
comms = sorted(comms, key=lambda c: [e for e in c['sig'] if isinstance(e, str)])
|
||||
return comms
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(json.dumps(gen_commands_dicts(), indent=2, sort_keys=True))
|
Loading…
Reference in New Issue
Block a user