#!/usr/bin/env python # Licensed to Cloudera, Inc. under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. Cloudera, Inc. 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. """ Registry for the applications """ import errno import glob import logging import os import simplejson import common LOG = logging.getLogger(__name__) class AppRegistry(object): """ Represents a registry. """ def __init__(self): """Open the existing registry""" self._reg_path = os.path.join(common.INSTALL_ROOT, 'app.reg') self._initialized = False self._apps = { } # Map of name -> HueApp self._open() def _open(self): """Open the registry file. May raise OSError""" if os.path.exists(self._reg_path): reg_file = file(self._reg_path) app_list = simplejson.load(reg_file) reg_file.close() for app_json in app_list: app_json.setdefault('author', 'Unknown') # Added after 0.9 app = HueApp.create(app_json) self._apps[app.name] = app self._initialized = True def _write(self, path): """Write out the registry to the given path""" outfile = file(path, 'w') simplejson.dump(self._apps.values(), outfile, cls=AppJsonEncoder, indent=2) outfile.close() def contains(self, app): """Returns whether the app (of the same version) is in the registry""" try: existing = self._apps[app.name] return existing.version == app.version except KeyError: return False def register(self, app): """register(app) -> True/False""" assert self._initialized, "Registry not yet initialized" try: existing = self._apps[app.name] version_diff = common.cmp_version(existing.version, app.version) if version_diff == 0: LOG.warn('%s is already registered' % (app,)) return False elif version_diff < 0: LOG.info('Upgrading %s from version %s' % (app, existing.version)) elif version_diff > 0: LOG.error('A newer version of %s is already installed' % (app,)) return False except KeyError: pass LOG.info('Updating registry with %s' % (app,)) self._apps[app.name] = app return True def unregister(self, app_name): """unregister(app_Name) -> HueApp. May raise KeyError""" assert self._initialized, "Registry not yet initialized" app = self._apps[app_name] del self._apps[app_name] return app def get_all_apps(self): """get_all_apps() -> List of HueApp""" return self._apps.values() def save(self): """Save and write out the registry""" assert self._initialized, "Registry not yet initialized" tmp_path = self._reg_path + '.new' self._write(tmp_path) os.rename(tmp_path, self._reg_path) LOG.info('=== Saved registry at %s' % (self._reg_path,)) class HueApp(object): """ Represents an app. """ @staticmethod def create(json): return HueApp(json['name'], json['version'], json['path'], json['desc'], json['author']) def __init__(self, name, version, path, desc, author): self.name = name self.version = version self.path = path self.desc = desc self.author = author def __str__(self): return "%s (version %s)" % (self.name, self.version) def __cmp__(self, other): if not isinstance(other, HueApp): raise TypeError return cmp((self.name, self.version), (other.name, other.version)) def jsonable(self): return dict(name=self.name, version=self.version, path=self.path, desc=self.desc, author=self.author) def find_ext_pys(self): """find_ext_pys() -> A list of paths for all ext-py packages""" return glob.glob(os.path.join(self.path, 'ext-py', '*')) def get_conffiles(self): """get_conffiles() -> A list of config (.ini) files""" ini_files = glob.glob(os.path.join(self.path, 'conf', '*.ini')) return [ os.path.abspath(ini) for ini in ini_files ] def install_conf(self): """ install_conf() -> True/False Symlink the app's conf/*.ini files into the conf directory. """ installed = [ ] for target in self.get_conffiles(): link_name = os.path.join(common.HUE_CONF_DIR, os.path.basename(target)) try: os.symlink(target, link_name) LOG.info('Symlink config %s -> %s' % (link_name, target)) installed.append(link_name) except OSError, ex: # Does the link already exists? if ex.errno == errno.EEXIST and os.path.islink(link_name): try: cur = os.readlink(link_name) if cur == target: LOG.warn("Symlink for configuration already exists: %s" % (link_name,)) continue except: pass # Nope. True error. Cleanup. LOG.error("Failed to symlink %s to %s: %s" % (target, link_name, ex)) for lnk in installed: try: os.unlink(lnk) except: LOG.error("Failed to cleanup link %s" % (link_name,)) return False return True def uninstall_conf(self): """uninstall_conf() -> True/False""" app_conf_dir = os.path.join(self.path, 'conf') # Check all symlink in the conf dir and remove any that point to this app for name in os.listdir(common.HUE_CONF_DIR): path = os.path.join(common.HUE_CONF_DIR, name) if not os.path.islink(path): continue target = os.readlink(path) if os.path.samefile(os.path.dirname(target), app_conf_dir): try: os.unlink(path) LOG.info('Remove config symlink %s -> %s' % (path, target)) except OSError, ex: LOG.error("Failed to remove configuration link %s: %s" % (path, ex)) return False return True class AppJsonEncoder(simplejson.JSONEncoder): def __init__(self, **kwargs): simplejson.JSONEncoder.__init__(self, **kwargs) def default(self, obj): if isinstance(obj, HueApp): return obj.jsonable() return simplejson.JSONEncoder.default(self, obj)