summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2016-06-21 12:07:21 +0200
committerJan Cholasta <jcholast@redhat.com>2016-06-28 13:30:49 +0200
commit4284d4fb4da1049c8b9f23d838f963b301aef97d (patch)
tree26b1a69fc7438923fea34daf50140a1058af1fac /ipalib
parent79d1f5833547044a7cb2700454cacb2a0976dd5f (diff)
downloadfreeipa-4284d4fb4da1049c8b9f23d838f963b301aef97d.tar.gz
freeipa-4284d4fb4da1049c8b9f23d838f963b301aef97d.tar.xz
freeipa-4284d4fb4da1049c8b9f23d838f963b301aef97d.zip
plugable: support plugin versioning
Allow multiple incompatible versions of a plugin using the same name. The current plugins are assumed to be version '1'. The unique identifier of plugins was changed from plugin name to plugin name and version. By default, the highest version available at build time is used. If the plugin is an unknown remote plugin, version of '1' is used by default. https://fedorahosted.org/freeipa/ticket/4427 Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/cli.py4
-rw-r--r--ipalib/frontend.py10
-rw-r--r--ipalib/plugable.py40
3 files changed, 48 insertions, 6 deletions
diff --git a/ipalib/cli.py b/ipalib/cli.py
index f60dc927d..1faf8285c 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -728,6 +728,8 @@ class help(frontend.Local):
# build help topics
for c in self.api.Command():
+ if c is not self.api.Command[c.name]:
+ continue
if c.NO_CLI:
continue
@@ -792,6 +794,8 @@ class help(frontend.Local):
elif name == "commands":
mcl = 0
for cmd in self.Command():
+ if cmd is not self.Command[cmd.name]:
+ continue
if cmd.NO_CLI:
continue
mcl = max(mcl, len(cmd.name))
diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index 71db84e3e..edea20866 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -1258,6 +1258,8 @@ class Object(HasParam):
namespace = self.api[name]
assert type(namespace) is APINameSpace
for plugin in namespace(): # Equivalent to dict.itervalues()
+ if plugin is not namespace[plugin.name]:
+ continue
if plugin.obj_name == self.name:
yield plugin
@@ -1328,11 +1330,17 @@ class Attribute(Plugin):
In practice the `Attribute` class is not used directly, but rather is
only the base class for the `Method` class. Also see the `Object` class.
"""
+ obj_version = '1'
+
@property
def obj_name(self):
return self.name.partition('_')[0]
@property
+ def obj_full_name(self):
+ return self.obj.full_name
+
+ @property
def attr_name(self):
prefix = '{}_'.format(self.obj_name)
assert self.name.startswith(prefix)
@@ -1340,7 +1348,7 @@ class Attribute(Plugin):
@property
def obj(self):
- return self.api.Object[self.obj_name]
+ return self.api.Object[self.obj_name, self.obj_version]
class Method(Attribute, Command):
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index 575e9bd63..d55a8f7a4 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -25,6 +25,7 @@ you are unfamiliar with this Python feature, see
http://docs.python.org/ref/sequence-types.html
"""
+from distutils.version import LooseVersion
import operator
import sys
import threading
@@ -47,7 +48,7 @@ from ipapython.ipa_log_manager import (
log_mgr,
LOGGING_FORMAT_FILE,
LOGGING_FORMAT_STDERR)
-from ipapython.version import VERSION, API_VERSION
+from ipapython.version import VERSION, API_VERSION, DEFAULT_PLUGINS
if six.PY3:
unicode = str
@@ -125,6 +126,8 @@ class Plugin(ReadOnly):
Base class for all plugins.
"""
+ version = '1'
+
def __init__(self, api):
assert api is not None
self.__api = api
@@ -141,6 +144,12 @@ class Plugin(ReadOnly):
name = classproperty(__name_getter)
@classmethod
+ def __full_name_getter(cls):
+ return '{}/{}'.format(cls.name, cls.version)
+
+ full_name = classproperty(__full_name_getter)
+
+ @classmethod
def __bases_getter(cls):
return cls.__bases__
@@ -278,6 +287,7 @@ class APINameSpace(collections.Mapping):
if self.__plugins is not None and self.__plugins_by_key is not None:
return
+ default_map = self.__api._API__default_map
plugins = set()
key_dict = self.__plugins_by_key = {}
@@ -286,9 +296,12 @@ class APINameSpace(collections.Mapping):
continue
plugins.add(plugin)
key_dict[plugin] = plugin
- key_dict[plugin.name] = plugin
+ key_dict[plugin.name, plugin.version] = plugin
+ key_dict[plugin.full_name] = plugin
+ if plugin.version == default_map.get(plugin.name, '1'):
+ key_dict[plugin.name] = plugin
- self.__plugins = sorted(plugins, key=operator.attrgetter('name'))
+ self.__plugins = sorted(plugins, key=operator.attrgetter('full_name'))
def __len__(self):
self.__enumerate()
@@ -326,6 +339,7 @@ class API(ReadOnly):
super(API, self).__init__()
self.__plugins = set()
self.__plugins_by_key = {}
+ self.__default_map = {}
self.__instances = {}
self.__next = {}
self.__done = set()
@@ -645,7 +659,7 @@ class API(ReadOnly):
)
# Check override:
- prev = self.__plugins_by_key.get(plugin.name)
+ prev = self.__plugins_by_key.get(plugin.full_name)
if prev:
if not override:
# Must use override=True to override:
@@ -668,7 +682,7 @@ class API(ReadOnly):
# The plugin is okay, add to sub_d:
self.__plugins.add(plugin)
- self.__plugins_by_key[plugin.name] = plugin
+ self.__plugins_by_key[plugin.full_name] = plugin
def finalize(self):
"""
@@ -680,6 +694,22 @@ class API(ReadOnly):
self.__doing('finalize')
self.__do_if_not_done('load_plugins')
+ for plugin in self.__plugins:
+ if not self.env.validate_api:
+ if plugin.full_name not in DEFAULT_PLUGINS:
+ continue
+ else:
+ try:
+ default_version = self.__default_map[plugin.name]
+ except KeyError:
+ pass
+ else:
+ version = LooseVersion(plugin.version)
+ default_version = LooseVersion(default_version)
+ if version < default_version:
+ continue
+ self.__default_map[plugin.name] = plugin.version
+
production_mode = self.is_production_mode()
for base in self.bases: