mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-22 15:13:50 -06:00
c553e3ceb2
The notes that Param pages will contain after #6733 are added manually, and because of it we need to add markers to differentiate between automated and manual content, equal to what we do for class pages. Signed-off-by: Antonio Torres <antorres@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
736 lines
25 KiB
Python
736 lines
25 KiB
Python
#!/usr/bin/python3
|
|
# Authors:
|
|
# Rob Crittenden <rcritten@redhat.com>
|
|
# John Dennis <jdennis@redhat.com>
|
|
# Martin Kosek <mkosek@redhat.com>
|
|
#
|
|
# Copyright (C) 2011 Red Hat
|
|
# see file 'COPYING' for use and warranty information
|
|
#
|
|
# 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 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Test the API against a known-good API to ensure that changes aren't made
|
|
# lightly.
|
|
|
|
from __future__ import print_function
|
|
|
|
import importlib
|
|
import itertools
|
|
import sys
|
|
import os
|
|
import re
|
|
import inspect
|
|
import operator
|
|
|
|
from ipalib import api
|
|
from ipalib.parameters import Param
|
|
from ipalib.output import Output
|
|
from ipalib.text import Gettext, NGettext, ConcatenatedLazyText
|
|
from ipalib.capabilities import capabilities
|
|
|
|
API_FILE='API.txt'
|
|
|
|
API_FILE_DIFFERENCE = 1
|
|
API_NEW_COMMAND = 2
|
|
API_NO_FILE = 4
|
|
API_DOC_ERROR = 8
|
|
|
|
# attributes removed from Param.__kw dictionary
|
|
PARAM_IGNORED_KW_ATTRIBUTES = (
|
|
'attribute',
|
|
'cli_metavar',
|
|
'default_from',
|
|
'doc',
|
|
'exclude',
|
|
'exponential',
|
|
'flags',
|
|
'hint',
|
|
'include',
|
|
'label',
|
|
'length',
|
|
'maxlength',
|
|
'maxvalue',
|
|
'minlength',
|
|
'minvalue',
|
|
'noextrawhitespace',
|
|
'normalizer',
|
|
'numberclass',
|
|
'only_absolute',
|
|
'only_relative',
|
|
'pattern',
|
|
'pattern_errmsg',
|
|
'precision',
|
|
'primary_key',
|
|
'require_service',
|
|
'query',
|
|
'sortorder',
|
|
)
|
|
|
|
# attributes removed from Output object
|
|
OUTPUT_IGNORED_ATTRIBUTES = (
|
|
'doc',
|
|
'flags',
|
|
)
|
|
|
|
def parse_options():
|
|
from optparse import OptionParser # pylint: disable=deprecated-module
|
|
|
|
parser = OptionParser()
|
|
parser.add_option("--validate", dest="validate", action="store_true",
|
|
default=False, help="Validate the API vs the stored API")
|
|
|
|
parser.add_option("--no-validate-doc", dest="validate_doc", action="store_false",
|
|
default=True, help="Do not validate documentation")
|
|
|
|
options, args = parser.parse_args()
|
|
return options, args
|
|
|
|
def param_repr(p):
|
|
"""
|
|
Return parameter repr() for API.txt purposes.
|
|
|
|
Some Param attributes do not cause API incompatibility (e.g. doc,
|
|
label or callables) and should not be added to API.txt. These attributes
|
|
are removed from the parameter before repr() is called.
|
|
|
|
NOTE: since the parameter is not not deepcopy()'ed before attributes are
|
|
removed, the original parameter is changed in the process. This is OK
|
|
for ./makeapi since we don't need this attributes anyway (except for
|
|
validate_doc() which is, however, called before any param_repr() call).
|
|
"""
|
|
if isinstance(p, Output):
|
|
for attr in OUTPUT_IGNORED_ATTRIBUTES:
|
|
try:
|
|
object.__delattr__(p, attr)
|
|
except AttributeError:
|
|
pass
|
|
return repr(p)
|
|
elif isinstance(p, Param):
|
|
param_kw = p.__dict__['_Param__kw']
|
|
for attr in PARAM_IGNORED_KW_ATTRIBUTES:
|
|
try:
|
|
del param_kw[attr]
|
|
except KeyError:
|
|
pass
|
|
object.__setattr__(p, 'rules', {})
|
|
return repr(p)
|
|
else:
|
|
raise ValueError('Unsupported parameter type!')
|
|
|
|
def validate_doc():
|
|
"""
|
|
Iterate over all API commands and perform the following validation:
|
|
|
|
* Every command must have documentation
|
|
and it must be marked for international translation
|
|
|
|
* Every module hosting a command must have documentation
|
|
and it must be marked for international translation
|
|
|
|
* Every module topic must be marked for international translation
|
|
|
|
For every error found emit a diagnostic.
|
|
Emit a summary of total errors found.
|
|
|
|
Return error flag if errors found, zero otherwise.
|
|
"""
|
|
|
|
def is_i18n(obj):
|
|
'Helper utility to determine if object has been internationalized'
|
|
return isinstance(obj, (Gettext, NGettext, ConcatenatedLazyText))
|
|
|
|
# The return value
|
|
rval = 0
|
|
|
|
# Used to track if we've processed a module already
|
|
topics = {}
|
|
|
|
# Initialize error counters
|
|
n_missing_cmd_doc = 0
|
|
n_missing_cmd_i18n = 0
|
|
n_missing_mod_doc = 0
|
|
n_missing_mod_i18n = 0
|
|
|
|
# Iterate over every command
|
|
for cmd in api.Command():
|
|
cmd_class = cmd.__class__
|
|
|
|
# Have we processed this module yet?
|
|
topic = cmd.topic
|
|
while topic is not None:
|
|
if not topics.setdefault(topic, 0):
|
|
# First time seeing this module, validate the module contents
|
|
doc = None
|
|
next_topic = None
|
|
|
|
for package in api.packages:
|
|
module = f'{package.__name__}.{topic}'
|
|
try:
|
|
mod = sys.modules[module]
|
|
except KeyError:
|
|
try:
|
|
mod = importlib.import_module(module)
|
|
except ImportError:
|
|
continue
|
|
|
|
if mod.__doc__ is not None:
|
|
doc = mod.__doc__
|
|
|
|
# See if there is a module topic, if so validate it
|
|
try:
|
|
next_topic = mod.topic
|
|
except AttributeError:
|
|
pass
|
|
|
|
# Does the module have documentation?
|
|
if doc is None:
|
|
src_file = inspect.getsourcefile(mod)
|
|
n_missing_mod_doc += 1
|
|
print("%s: module \"%s\" has no doc" %
|
|
(src_file, module))
|
|
# Yes the module has doc, but is it internationalized?
|
|
elif not is_i18n(doc):
|
|
src_file = inspect.getsourcefile(cmd_class)
|
|
n_missing_mod_i18n += 1
|
|
print("%s: module \"%s\" doc is not internationalized" %
|
|
(src_file, module))
|
|
else:
|
|
next_topic = None
|
|
|
|
# Increment the count of how many commands in this module
|
|
topics[topic] = topics[topic] + 1
|
|
|
|
topic = next_topic
|
|
|
|
# Does the command have documentation?
|
|
if cmd.doc is None:
|
|
src_file = inspect.getsourcefile(cmd_class)
|
|
line_num = inspect.getsourcelines(cmd_class)[1]
|
|
n_missing_cmd_doc += 1
|
|
print("%s:%d command \"%s\" has no doc" % (src_file, line_num, cmd.name))
|
|
# Yes the command has doc, but is it internationalized?
|
|
elif not is_i18n(cmd.doc):
|
|
src_file = inspect.getsourcefile(cmd_class)
|
|
line_num = inspect.getsourcelines(cmd_class)[1]
|
|
n_missing_cmd_i18n += 1
|
|
print("%s:%d command \"%s\" doc is not internationalized" % (src_file, line_num, cmd.name))
|
|
|
|
# If any errors, emit summary information and adjust return value
|
|
if n_missing_cmd_doc > 0 or n_missing_cmd_i18n > 0:
|
|
rval = API_DOC_ERROR
|
|
print("%d commands without doc, %d commands whose doc is not i18n" % \
|
|
(n_missing_cmd_doc, n_missing_cmd_i18n))
|
|
|
|
if n_missing_mod_doc > 0 or n_missing_mod_i18n > 0:
|
|
rval = API_DOC_ERROR
|
|
print("%d modules without doc, %d modules whose doc is not i18n" % \
|
|
(n_missing_mod_doc, n_missing_mod_i18n))
|
|
|
|
return rval
|
|
|
|
def make_api():
|
|
"""
|
|
Write a new API file from the current tree.
|
|
"""
|
|
fd = open(API_FILE, 'w')
|
|
for cmd in api.Command():
|
|
fd.write('command: %s\n' % cmd.full_name)
|
|
fd.write('args: %d,%d,%d\n' % (len(cmd.args), len(cmd.options), len(cmd.output)))
|
|
for a in cmd.args():
|
|
fd.write('arg: %s\n' % param_repr(a))
|
|
for o in sorted(cmd.options(), key=operator.attrgetter('name')):
|
|
fd.write('option: %s\n' % param_repr(o))
|
|
for o in sorted(cmd.output(), key=operator.attrgetter('name')):
|
|
fd.write('output: %s\n' % param_repr(o))
|
|
for plugin in sorted(itertools.chain(api.Command(), api.Object()),
|
|
key=operator.attrgetter('full_name')):
|
|
try:
|
|
default_plugin = api.Command[plugin.name]
|
|
except KeyError:
|
|
default_plugin = api.Object[plugin.name]
|
|
if plugin is default_plugin:
|
|
fd.write('default: %s\n' % plugin.full_name)
|
|
for name, version in sorted(
|
|
capabilities.items(), key=operator.itemgetter(1, 0)):
|
|
fd.write('capability: %s %s\n' % (name, version))
|
|
fd.close()
|
|
|
|
return 0
|
|
|
|
|
|
def make_api_reference(validate_only=False):
|
|
"""
|
|
Generate API reference.
|
|
Index will be created in reStructuredText format so that it can be
|
|
integrated in existing documentation. Then, reference for each class
|
|
will be created in Markdown format.
|
|
"""
|
|
|
|
# Markers to separate automated content from manual notes
|
|
automated_marker_start = "[//]: # (THE CONTENT BELOW IS GENERATED. DO NOT EDIT.)" # noqa: E501
|
|
automated_marker_end = "[//]: # (ADD YOUR NOTES BELOW. THESE WILL BE PICKED EVERY TIME THE DOCS ARE REGENERATED. //end)" # noqa: E501
|
|
|
|
class_template = """{automated_marker_start}
|
|
{reference}
|
|
|
|
{automated_marker_end}
|
|
{notes}
|
|
"""
|
|
|
|
notes_template = """### Semantics
|
|
|
|
### Notes
|
|
|
|
### Version differences
|
|
"""
|
|
|
|
def make_md_table(rows):
|
|
"""
|
|
First list passed are column names, the rest are rows.
|
|
Return list of lines conforming the table.
|
|
"""
|
|
out = ["|" + "|".join(rows[0])]
|
|
out.append("|" + "|".join(['-'] * len(rows[0])))
|
|
for row in rows[1:]:
|
|
out.append("|" + "|".join(row))
|
|
return out
|
|
|
|
cmd_lines = [
|
|
"IPA API Commands",
|
|
"=================",
|
|
".. toctree::",
|
|
" :maxdepth: 1",
|
|
"\n"
|
|
]
|
|
|
|
param_lines = [
|
|
"IPA API Parameter types",
|
|
"=======================",
|
|
".. toctree::",
|
|
" :maxdepth: 1",
|
|
"\n"
|
|
]
|
|
|
|
# Create Markdown file for each parameter type under ipalib.parameters.Param
|
|
def all_subclasses(cls):
|
|
ret = cls.__subclasses__()
|
|
for s in cls.__subclasses__():
|
|
ret.extend(all_subclasses(s))
|
|
return sorted(list(set(ret)), key=operator.attrgetter('__name__'))
|
|
|
|
api_contents = {}
|
|
|
|
ipa_classes = all_subclasses(Param)
|
|
|
|
for param in ipa_classes:
|
|
lines = [
|
|
automated_marker_start,
|
|
".. _%s:\n" % param.__name__,
|
|
"# %s" % param.__name__,
|
|
automated_marker_end
|
|
]
|
|
|
|
try:
|
|
with open("doc/api/%s.md" % param.__name__, "r") as f:
|
|
# Read notes written to page
|
|
notes = f.read().split("//end)")[1].strip()
|
|
except FileNotFoundError:
|
|
notes = ""
|
|
|
|
lines.append(notes)
|
|
|
|
api_contents["doc/api/%s.md" % param.__name__] = "\n".join(lines)
|
|
param_lines.append(" %s.md" % param.__name__)
|
|
|
|
api_contents["doc/api/parameters.rst"] = "\n".join(param_lines)
|
|
|
|
def generate_param_type_text(obj):
|
|
# If class is part of IPA Params, return text with ref, if not just return its name
|
|
if type(obj) in ipa_classes:
|
|
return ":ref:`{n}<{n}>`".format(n=type(obj).__name__)
|
|
else:
|
|
return type(obj).__name__
|
|
|
|
# Create Markdown file for each command
|
|
for cmd in api.Command():
|
|
lines = []
|
|
lines.append("# %s" % cmd.name)
|
|
|
|
doc_stripped = '\n'.join([s.strip() for s in str(cmd.doc).splitlines()])
|
|
lines.append(doc_stripped)
|
|
|
|
lines.append("\n### Arguments")
|
|
try:
|
|
next(cmd.args()) # if empty, StopIteration is thrown
|
|
table_rows = [["Name", "Type", "Required"]]
|
|
for a in cmd.args():
|
|
table_rows.append([a.name, generate_param_type_text(a), str(a.required)])
|
|
lines.extend(make_md_table(table_rows))
|
|
except StopIteration:
|
|
lines.append("No arguments.")
|
|
|
|
lines.append("\n### Options")
|
|
try:
|
|
next(cmd.options())
|
|
for o in sorted(cmd.options(), key=operator.attrgetter('required'), reverse=True):
|
|
req_str = " **(Required)**" if o.required else ""
|
|
lines.append("* {} : {}{}".format(o.name, generate_param_type_text(o), req_str))
|
|
if hasattr(o, "default") and o.default is not None:
|
|
lines.append(" * Default: {}".format(o.default))
|
|
if hasattr(o, "values"):
|
|
lines.append(" * Values: {}".format(o.values))
|
|
except StopIteration:
|
|
lines.append("No options.")
|
|
|
|
lines.append("\n### Output")
|
|
try:
|
|
next(cmd.output())
|
|
table_rows = [["Name", "Type"]]
|
|
for o in sorted(cmd.output(), key=operator.attrgetter('name')):
|
|
table_rows.append([o.name, generate_param_type_text(o)])
|
|
lines.extend(make_md_table(table_rows))
|
|
except StopIteration:
|
|
lines.append("No output.")
|
|
|
|
cmd_lines.append(" %s.md" % cmd.name)
|
|
|
|
try:
|
|
with open("doc/api/%s.md" % cmd.name, "r") as f:
|
|
# Read notes written to template
|
|
notes = f.read().split("//end)")[1].strip()
|
|
except FileNotFoundError:
|
|
notes = notes_template
|
|
|
|
out = class_template.format(
|
|
automated_marker_start=automated_marker_start,
|
|
reference="\n".join(lines),
|
|
notes=notes,
|
|
automated_marker_end=automated_marker_end
|
|
).strip()
|
|
api_contents["doc/api/%s.md" % cmd.name] = out
|
|
|
|
api_contents["doc/api/commands.rst"] = "\n".join(cmd_lines)
|
|
|
|
if validate_only:
|
|
# Don't write to files, just return contents to be validated
|
|
return api_contents
|
|
else:
|
|
# Write all contents to their files
|
|
for file, contents in api_contents.items():
|
|
with open(file, "w") as f:
|
|
f.write(contents)
|
|
|
|
return 0
|
|
|
|
|
|
def find_name(line):
|
|
"""
|
|
Break apart a Param line and pull out the name. It would be nice if we
|
|
could just eval() the line but we wouldn't have defined any validators
|
|
or normalizers it may be using.
|
|
"""
|
|
m = re.match('^[a-zA-Z0-9]+\(\'([a-z][_a-z0-9?\*\+]*)\'.*', line)
|
|
if m:
|
|
name = m.group(1)
|
|
else:
|
|
print("Couldn't find name in: %s" % line)
|
|
name = ''
|
|
return name
|
|
|
|
def _finalize_command_validation(cmd, found_args, expected_args,
|
|
found_options, expected_options,
|
|
found_output, expected_output):
|
|
passed = True
|
|
# Check the args of the previous command.
|
|
if len(found_args) != expected_args:
|
|
print('Argument count in %s of %d doesn\'t match expected: %d' % (
|
|
cmd.name, len(found_args), expected_args))
|
|
passed = False
|
|
if len(found_options) != expected_options:
|
|
print('Options count in %s of %d doesn\'t match expected: %d' % (
|
|
cmd.name, len(found_options), expected_options))
|
|
passed = False
|
|
if len(found_output) != expected_output:
|
|
print('Output count in %s of %d doesn\'t match expected: %d' % (
|
|
cmd.name, len(found_output), expected_output))
|
|
passed = False
|
|
|
|
# Check if there is not a new arg/opt/output in previous command
|
|
for a in cmd.args():
|
|
if a.param_spec not in found_args:
|
|
print('Argument %s of command %s in ipalib, not in API file:\n%s' % (
|
|
a.param_spec, cmd.name, param_repr(a)))
|
|
passed = False
|
|
for o in cmd.options():
|
|
if o.param_spec not in found_options:
|
|
print('Option %s of command %s in ipalib, not in API file:\n%s' % (
|
|
o.param_spec, cmd.name, param_repr(o)))
|
|
passed = False
|
|
for o in cmd.output():
|
|
if o.name not in found_output:
|
|
print('Output %s of command %s in ipalib, not in API file:\n%s' % (
|
|
o.name, cmd.name, param_repr(o)))
|
|
passed = False
|
|
|
|
return passed
|
|
|
|
def validate_api():
|
|
"""
|
|
Compare the API in the file to the one in ipalib.
|
|
|
|
Return a bitwise return code to identify the types of errors found, if
|
|
any.
|
|
"""
|
|
fd = open(API_FILE, 'r')
|
|
lines = fd.readlines()
|
|
fd.close()
|
|
|
|
rval = 0
|
|
|
|
expected_args = 0
|
|
expected_options = 0
|
|
expected_output = 0
|
|
found_args = []
|
|
found_options = []
|
|
found_output = []
|
|
|
|
# First run through the file and compare it to the API
|
|
existing_cmds = []
|
|
existing_capabilities = set()
|
|
existing_defaults = set()
|
|
cmd = None
|
|
for line in lines:
|
|
line = line.strip()
|
|
if line.startswith('command:'):
|
|
if cmd:
|
|
if not _finalize_command_validation(cmd, found_args, expected_args,
|
|
found_options, expected_options,
|
|
found_output, expected_output):
|
|
rval |= API_FILE_DIFFERENCE
|
|
|
|
(arg, name) = line.split(': ', 1)
|
|
if name not in api.Command:
|
|
print("Command %s in API file, not in ipalib" % name)
|
|
rval |= API_FILE_DIFFERENCE
|
|
cmd = None
|
|
else:
|
|
existing_cmds.append(name)
|
|
cmd = api.Command[name]
|
|
found_args = []
|
|
found_options = []
|
|
found_output = []
|
|
if line.startswith('args:') and cmd:
|
|
line = line.replace('args: ', '')
|
|
(expected_args, expected_options, expected_output) = line.split(',')
|
|
expected_args = int(expected_args)
|
|
expected_options = int(expected_options)
|
|
expected_output = int(expected_output)
|
|
if line.startswith('arg:') and cmd:
|
|
line = line.replace('arg: ', '')
|
|
found = False
|
|
arg = find_name(line)
|
|
for a in cmd.args():
|
|
if param_repr(a) == line:
|
|
found = True
|
|
else:
|
|
if a.name == arg:
|
|
found = True
|
|
print('Arg in %s doesn\'t match.\nGot %s\nExpected %s' % (
|
|
name, param_repr(a), line))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if found:
|
|
found_args.append(arg)
|
|
else:
|
|
arg = find_name(line)
|
|
print("Argument '%s' in command '%s' in API file not found" % (arg, name))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if line.startswith('option:') and cmd:
|
|
line = line.replace('option: ', '')
|
|
found = False
|
|
option = find_name(line)
|
|
for o in cmd.options():
|
|
if param_repr(o) == line:
|
|
found = True
|
|
else:
|
|
if o.name == option:
|
|
found = True
|
|
print('Option in %s doesn\'t match. Got %s Expected %s' % (name, o, line))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if found:
|
|
found_options.append(option)
|
|
else:
|
|
option = find_name(line)
|
|
print("Option '%s' in command '%s' in API file not found" % (option, name))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if line.startswith('output:') and cmd:
|
|
line = line.replace('output: ', '')
|
|
found = False
|
|
output = find_name(line)
|
|
for o in cmd.output():
|
|
if param_repr(o) == line:
|
|
found = True
|
|
else:
|
|
if o.name == output:
|
|
found = True
|
|
print('Output in %s doesn\'t match. Got %s Expected %s' % (name, o, line))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if found:
|
|
found_output.append(output)
|
|
else:
|
|
output = find_name(line)
|
|
print("Option '%s' in command '%s' in API file not found" % (output, name))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if line.startswith('default:'):
|
|
default = line.replace('default: ', '')
|
|
existing_defaults.add(default)
|
|
default_name = None
|
|
for namespace in (api.Command, api.Object):
|
|
try:
|
|
default_name = namespace[default].name
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
break
|
|
else:
|
|
print("Plugin %s in API file, not in ipalib" % default)
|
|
rval |= API_FILE_DIFFERENCE
|
|
if default_name is not None:
|
|
try:
|
|
expected_default = namespace[default_name].full_name
|
|
except KeyError:
|
|
print("Default version of plugin %s in API file not "
|
|
"found" % default_name)
|
|
rval |= API_FILE_DIFFERENCE
|
|
else:
|
|
if default != expected_default:
|
|
print("Default version of plugin %s in API file "
|
|
"doesn't match. Got %s, expected %s." %
|
|
(default_name, default, expected_default))
|
|
rval |= API_FILE_DIFFERENCE
|
|
if line.startswith('capability:'):
|
|
cap, version = line.replace('capability: ', '').split(' ', 1)
|
|
existing_capabilities.add(cap)
|
|
try:
|
|
expected_version = str(capabilities[cap])
|
|
except KeyError:
|
|
print("Capability '%s' in API file not found" % cap)
|
|
rval |= API_FILE_DIFFERENCE
|
|
else:
|
|
if version != expected_version:
|
|
print((
|
|
"Capability '%s' in API file doesn't match. Got %s, "
|
|
"expected %s.") % (cap, version, expected_version))
|
|
rval |= API_FILE_DIFFERENCE
|
|
|
|
if cmd:
|
|
if not _finalize_command_validation(cmd, found_args, expected_args,
|
|
found_options, expected_options,
|
|
found_output, expected_output):
|
|
rval |= API_FILE_DIFFERENCE
|
|
|
|
# Now look for new commands not in the current API
|
|
for cmd in api.Command():
|
|
if cmd.full_name not in existing_cmds:
|
|
print("Command %s in ipalib, not in API" % cmd.full_name)
|
|
rval |= API_NEW_COMMAND
|
|
|
|
for namespace in (api.Command, api.Object):
|
|
for plugin in namespace():
|
|
if plugin.name in namespace and namespace[plugin.name] is cmd:
|
|
if plugin.full_name not in existing_defaults:
|
|
print("Default version of command %s in ipalib, not in "
|
|
"API" % plugin.name)
|
|
rval |= API_FILE_DIFFERENCE
|
|
|
|
for cap in capabilities:
|
|
if cap not in existing_capabilities:
|
|
print("Capability %s in ipalib, not in API" % cap)
|
|
rval |= API_FILE_DIFFERENCE
|
|
|
|
return rval
|
|
|
|
|
|
def validate_api_reference():
|
|
api_contents = make_api_reference(validate_only=True)
|
|
rval = 0
|
|
|
|
# Iterate current files, fail if any diference is found
|
|
for file, contents in api_contents.items():
|
|
try:
|
|
f = open(file, "r")
|
|
file_contents = f.read().split("//end)")[0].strip()
|
|
api_contents = contents.split("//end")[0].strip()
|
|
if file_contents != api_contents:
|
|
rval = 1
|
|
print("API Reference difference found in: ", file)
|
|
except FileNotFoundError:
|
|
rval = 1
|
|
print("File not found: ", file)
|
|
|
|
return rval
|
|
|
|
def main():
|
|
rval = 0
|
|
options, _args = parse_options()
|
|
|
|
cfg = dict(
|
|
in_server=True,
|
|
in_tree=True,
|
|
debug=False,
|
|
verbose=0,
|
|
validate_api=True,
|
|
enable_ra=True,
|
|
mode='developer',
|
|
plugins_on_demand=False,
|
|
realm="EXAMPLE.COM",
|
|
domain="example.com",
|
|
)
|
|
|
|
api.bootstrap(**cfg)
|
|
api.finalize()
|
|
|
|
if options.validate_doc:
|
|
rval |= validate_doc()
|
|
|
|
if options.validate:
|
|
if not os.path.exists(API_FILE):
|
|
print('No %s to validate' % API_FILE)
|
|
rval |= API_NO_FILE
|
|
else:
|
|
rval |= validate_api()
|
|
rval |= validate_api_reference()
|
|
else:
|
|
print("Writing API to API.txt")
|
|
rval |= make_api()
|
|
print("Creating API Reference")
|
|
rval |= make_api_reference()
|
|
|
|
if rval & API_FILE_DIFFERENCE:
|
|
print('')
|
|
print("There are one or more changes to the API.\n"
|
|
"Either undo the API changes or update API.txt, "
|
|
"API Reference, and increment the major version in VERSION.")
|
|
|
|
if rval & API_NEW_COMMAND:
|
|
print('')
|
|
print('There are one or more new commands defined.\nUpdate API.txt and increment the minor version in VERSION.')
|
|
|
|
if rval & API_DOC_ERROR:
|
|
print('')
|
|
print('There are one or more documentation problems.\nYou must fix these before preceeding')
|
|
|
|
return rval
|
|
|
|
sys.exit(main())
|