summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/orchestrator/plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'azure/aria/aria-extension-cloudify/src/aria/aria/orchestrator/plugin.py')
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/orchestrator/plugin.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/orchestrator/plugin.py b/azure/aria/aria-extension-cloudify/src/aria/aria/orchestrator/plugin.py
new file mode 100644
index 0000000..756a28e
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/orchestrator/plugin.py
@@ -0,0 +1,171 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Plugin management.
+"""
+
+import os
+import tempfile
+import subprocess
+import sys
+import zipfile
+from datetime import datetime
+
+import wagon
+
+from . import exceptions
+from ..utils import process as process_utils
+
+_IS_WIN = os.name == 'nt'
+
+
+class PluginManager(object):
+
+ def __init__(self, model, plugins_dir):
+ """
+ :param plugins_dir: root directory in which to install plugins
+ """
+ self._model = model
+ self._plugins_dir = plugins_dir
+
+ def install(self, source):
+ """
+ Install a wagon plugin.
+ """
+ metadata = wagon.show(source)
+ cls = self._model.plugin.model_cls
+
+ os_props = metadata['build_server_os_properties']
+
+ plugin = cls(
+ name=metadata['package_name'],
+ archive_name=metadata['archive_name'],
+ supported_platform=metadata['supported_platform'],
+ supported_py_versions=metadata['supported_python_versions'],
+ distribution=os_props.get('distribution'),
+ distribution_release=os_props['distribution_version'],
+ distribution_version=os_props['distribution_release'],
+ package_name=metadata['package_name'],
+ package_version=metadata['package_version'],
+ package_source=metadata['package_source'],
+ wheels=metadata['wheels'],
+ uploaded_at=datetime.now()
+ )
+ if len(self._model.plugin.list(filters={'package_name': plugin.package_name,
+ 'package_version': plugin.package_version})):
+ raise exceptions.PluginAlreadyExistsError(
+ 'Plugin {0}, version {1} already exists'.format(plugin.package_name,
+ plugin.package_version))
+ self._install_wagon(source=source, prefix=self.get_plugin_dir(plugin))
+ self._model.plugin.put(plugin)
+ return plugin
+
+ def load_plugin(self, plugin, env=None):
+ """
+ Load the plugin into an environment.
+
+ Loading the plugin means the plugin's code and binaries paths will be appended to the
+ environment's ``PATH`` and ``PYTHONPATH``, thereby allowing usage of the plugin.
+
+ :param plugin: plugin to load
+ :param env: environment to load the plugin into; If ``None``, :obj:`os.environ` will be
+ used
+ """
+ env = env or os.environ
+ plugin_dir = self.get_plugin_dir(plugin)
+
+ # Update PATH environment variable to include plugin's bin dir
+ bin_dir = 'Scripts' if _IS_WIN else 'bin'
+ process_utils.append_to_path(os.path.join(plugin_dir, bin_dir), env=env)
+
+ # Update PYTHONPATH environment variable to include plugin's site-packages
+ # directories
+ if _IS_WIN:
+ pythonpath_dirs = [os.path.join(plugin_dir, 'Lib', 'site-packages')]
+ else:
+ # In some linux environments, there will be both a lib and a lib64 directory
+ # with the latter, containing compiled packages.
+ pythonpath_dirs = [os.path.join(
+ plugin_dir, 'lib{0}'.format(b),
+ 'python{0}.{1}'.format(sys.version_info[0], sys.version_info[1]),
+ 'site-packages') for b in ('', '64')]
+
+ process_utils.append_to_pythonpath(*pythonpath_dirs, env=env)
+
+ def get_plugin_dir(self, plugin):
+ return os.path.join(
+ self._plugins_dir,
+ '{0}-{1}'.format(plugin.package_name, plugin.package_version))
+
+ @staticmethod
+ def validate_plugin(source):
+ """
+ Validate a plugin archive.
+
+ A valid plugin is a `wagon <http://github.com/cloudify-cosmo/wagon>`__ in the zip format
+ (suffix may also be ``.wgn``).
+ """
+ if not zipfile.is_zipfile(source):
+ raise exceptions.InvalidPluginError(
+ 'Archive {0} is of an unsupported type. Only '
+ 'zip/wgn is allowed'.format(source))
+ with zipfile.ZipFile(source, 'r') as zip_file:
+ infos = zip_file.infolist()
+ try:
+ package_name = infos[0].filename[:infos[0].filename.index('/')]
+ package_json_path = "{0}/{1}".format(package_name, 'package.json')
+ zip_file.getinfo(package_json_path)
+ except (KeyError, ValueError, IndexError):
+ raise exceptions.InvalidPluginError(
+ 'Failed to validate plugin {0} '
+ '(package.json was not found in archive)'.format(source))
+
+ def _install_wagon(self, source, prefix):
+ pip_freeze_output = self._pip_freeze()
+ file_descriptor, constraint_path = tempfile.mkstemp(prefix='constraint-', suffix='.txt')
+ os.close(file_descriptor)
+ try:
+ with open(constraint_path, 'wb') as constraint:
+ constraint.write(pip_freeze_output)
+ # Install the provided wagon.
+ # * The --prefix install_arg will cause the plugin to be installed under
+ # plugins_dir/{package_name}-{package_version}, So different plugins don't step on
+ # each other and don't interfere with the current virtualenv
+ # * The --constraint flag points a file containing the output of ``pip freeze``.
+ # It is required, to handle cases where plugins depend on some python package with
+ # a different version than the one installed in the current virtualenv. Without this
+ # flag, the existing package will be **removed** from the parent virtualenv and the
+ # new package will be installed under prefix. With the flag, the existing version will
+ # remain, and the version requested by the plugin will be ignored.
+ wagon.install(
+ source=source,
+ install_args='--prefix="{prefix}" --constraint="{constraint}"'.format(
+ prefix=prefix,
+ constraint=constraint.name),
+ venv=os.environ.get('VIRTUAL_ENV'))
+ finally:
+ os.remove(constraint_path)
+
+ @staticmethod
+ def _pip_freeze():
+ """Run pip freeze in current environment and return the output"""
+ bin_dir = 'Scripts' if os.name == 'nt' else 'bin'
+ pip_path = os.path.join(sys.prefix, bin_dir,
+ 'pip{0}'.format('.exe' if os.name == 'nt' else ''))
+ pip_freeze = subprocess.Popen([pip_path, 'freeze'], stdout=subprocess.PIPE)
+ pip_freeze_output, _ = pip_freeze.communicate()
+ assert not pip_freeze.poll()
+ return pip_freeze_output