Add client capabilities, enable messages

The API version the client sends can now be used to check what the client
expects or is capable of.

All version tests IPA does will be be named and listed in one module,
ipalib.capabilities, which includes a function to test a specific capability
against an API version.
Similarly to Python's __future__ module, capabilities.py also serves as
documentation of backwards-incompatible changes to the API.

The first capability to be defined is "messages". Recent enough clients can
accept a list of warnings or other info under the "messages" key in the
result dict.

If a JSON client does not send the API version, it is assumed this is a testing
client (e.g. curl from the command line). Such a client "has" all capabilities,
but it will always receive a warning mentioning that forward compatibility
is not guaranteed.
If a XML client does not send the API version, it is assumed it uses the API
version before capabilities were introduced. (This is to keep backwards
compatibility with clients containing bug https://fedorahosted.org/freeipa/ticket/3294)

Whenever a capability is added, the API version must be incremented.
To ensure that, capabilities are written to API.txt and checked by
`makeapi --validate`.

Design page: http://freeipa.org/page/V3/Messages
Ticket: https://fedorahosted.org/freeipa/ticket/2732
This commit is contained in:
Petr Viktorin
2013-02-21 16:26:09 +01:00
committed by Martin Kosek
parent 8af5369cba
commit 24bca144a8
13 changed files with 232 additions and 16 deletions
+24
View File
@@ -32,6 +32,7 @@ from ipalib import api
from ipalib.parameters import Param
from ipalib.output import Output
from ipalib.text import Gettext, NGettext
from ipalib.capabilities import capabilities
API_FILE='API.txt'
@@ -211,6 +212,9 @@ def make_api():
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 name, version in sorted(
capabilities.items(), key=lambda (k, v): (v, k)):
fd.write('capability: %s %s\n' % (name, version))
fd.close()
return 0
@@ -288,6 +292,7 @@ def validate_api():
# First run through the file and compare it to the API
existing_cmds = []
existing_capabilities = set()
cmd = None
for line in lines:
line = line.strip()
@@ -370,6 +375,20 @@ def validate_api():
output = find_name(line)
print "Option '%s' in command '%s' in API file not found" % (output, name)
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,
@@ -383,6 +402,11 @@ def validate_api():
print "Command %s in ipalib, not in API" % cmd.name
rval |= API_NEW_COMMAND
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 main():