summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
authorPetr Viktorin <pviktori@redhat.com>2012-12-07 10:54:07 -0500
committerMartin Kosek <mkosek@redhat.com>2013-02-21 16:26:09 +0100
commit24bca144a8049cea8683afd699d2e0e158b5f164 (patch)
tree9314443e230d79cb8c7930ce73842036b3b8c054 /ipalib
parent8af5369cba1ff0e6d8baae90f3d93b40e91e85d6 (diff)
downloadfreeipa-24bca144a8049cea8683afd699d2e0e158b5f164.tar.gz
freeipa-24bca144a8049cea8683afd699d2e0e158b5f164.tar.xz
freeipa-24bca144a8049cea8683afd699d2e0e158b5f164.zip
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
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/capabilities.py50
-rw-r--r--ipalib/frontend.py40
-rw-r--r--ipalib/messages.py6
3 files changed, 88 insertions, 8 deletions
diff --git a/ipalib/capabilities.py b/ipalib/capabilities.py
new file mode 100644
index 000000000..751b93e2b
--- /dev/null
+++ b/ipalib/capabilities.py
@@ -0,0 +1,50 @@
+# Authors:
+# Petr Viktorin <pviktori@redhat.com>
+#
+# Copyright (C) 2012 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/>.
+
+"""List of, and utilities for working with, client capabilities by API version
+
+The API version is given in ipapython.version.API_VERSION.
+
+This module defines a dict, ``capabilities``, that maps feature names to API
+versions they were introduced in.
+"""
+
+from distutils import version
+
+VERSION_WITHOUT_CAPABILITIES = u'2.51'
+
+capabilities = dict(
+ # messages: Server output may include an extra key, "messages", that
+ # contains a list of warnings and other messages.
+ # http://freeipa.org/page/V3/Messages
+ messages=u'2.52',
+
+)
+
+
+def client_has_capability(client_version, capability):
+ """Determine whether the client has the given capability
+
+ :param capability: Name of the capability to test
+ :param client_version: The API version string reported by the client
+ """
+
+ version_tuple = version.LooseVersion(client_version)
+
+ return version_tuple >= version.LooseVersion(capabilities[capability])
diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index c27ff1389..06259823c 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -23,18 +23,19 @@ Base classes for all front-end plugins.
import re
import inspect
+from distutils import version
+
+from ipapython.version import API_VERSION
+from ipapython.ipa_log_manager import root_logger
from base import lock, check_name, NameSpace
from plugable import Plugin, is_production_mode
from parameters import create_param, parse_param_spec, Param, Str, Flag, Password
from output import Output, Entry, ListOfEntries
from text import _, ngettext
-
from errors import (ZeroArgumentError, MaxArgumentError, OverlapError,
- RequiresRoot, VersionError, RequirementError, OptionError)
-from errors import InvocationError
+ RequiresRoot, VersionError, RequirementError, OptionError, InvocationError)
from constants import TYPE_ERROR
-from ipapython.version import API_VERSION
-from distutils import version
+from ipalib import messages
RULE_FLAG = 'validation_rule'
@@ -740,11 +741,17 @@ class Command(HasParam):
performs is executed remotely.
"""
if self.api.env.in_server:
- if 'version' in options:
+ version_provided = 'version' in options
+ if version_provided:
self.verify_client_version(options['version'])
else:
options['version'] = API_VERSION
- return self.execute(*args, **options)
+ result = self.execute(*args, **options)
+ if not version_provided:
+ messages.add_message(
+ API_VERSION, result,
+ messages.VersionMissing(server_version=API_VERSION))
+ return result
return self.forward(*args, **options)
def execute(self, *args, **kw):
@@ -914,7 +921,7 @@ class Command(HasParam):
nice, dict, type(output), output)
)
expected_set = set(self.output)
- actual_set = set(output)
+ actual_set = set(output) - set(['messages'])
if expected_set != actual_set:
missing = expected_set - actual_set
if missing:
@@ -945,6 +952,21 @@ class Command(HasParam):
continue
yield param
+ def log_messages(self, output, logger):
+ logger_functions = dict(
+ debug=logger.debug,
+ info=logger.info,
+ warning=logger.warning,
+ error=logger.error,
+ )
+ for message in output.get('messages', ()):
+ try:
+ function = logger_functions[message['type']]
+ except KeyError:
+ logger.error('Server sent a message with a wrong type')
+ function = logger.error
+ function(message.get('message'))
+
def output_for_cli(self, textui, output, *args, **options):
"""
Generic output method. Prints values the output argument according
@@ -963,6 +985,8 @@ class Command(HasParam):
rv = 0
+ self.log_messages(output, root_logger)
+
order = [p.name for p in self.output_params()]
if options.get('all', False):
order.insert(0, 'dn')
diff --git a/ipalib/messages.py b/ipalib/messages.py
index 619e81d53..e5b76a526 100644
--- a/ipalib/messages.py
+++ b/ipalib/messages.py
@@ -35,6 +35,12 @@ from inspect import isclass
from ipalib.constants import TYPE_ERROR
from ipalib.text import _ as ugettext
from ipalib.text import Gettext, NGettext
+from ipalib.capabilities import client_has_capability
+
+
+def add_message(version, result, message):
+ if client_has_capability(version, 'messages'):
+ result.setdefault('messages', []).append(message.to_dict())
def process_message_arguments(obj, format=None, message=None, **kw):