瀏覽代碼

HUE-486. Enable renaming of top-level configs, and add a path for performing search/replace config upgrades

Jon Natkins 14 年之前
父節點
當前提交
502f42f02c

+ 16 - 1
desktop/core/src/desktop/appmanager.py

@@ -91,8 +91,18 @@ class DesktopModuleInfo(object):
     module.  Static directories, settings, urls, etc.
     module.  Static directories, settings, urls, etc.
     will be found based on that module.
     will be found based on that module.
     """
     """
+
+    # For clarification purposes, all of these different names need a
+    # bit of explanation. The name is the actual name of the application.
+    # The display name is used by dump_config, and will either be the
+    # app name or the config key, if the config key has been defined in the
+    # app's settings.  Mostly, it's around for consistency's sake.
+    # The nice name is just a more formal name, i.e. useradmin might 
+    # have a nice name of User Administration Tool, or something 
+    # similarly flowery.
     self.module = module
     self.module = module
     self.name = module.__name__
     self.name = module.__name__
+    self.display_name = module.__name__
 
 
     # Set up paths
     # Set up paths
     module_root = os.path.dirname(module.__file__)
     module_root = os.path.dirname(module.__file__)
@@ -100,12 +110,15 @@ class DesktopModuleInfo(object):
 
 
     # Load application settings
     # Load application settings
     self._load_settings_module(module.__name__ + ".settings")
     self._load_settings_module(module.__name__ + ".settings")
-
+    
     if hasattr(self.settings, "NICE_NAME"):
     if hasattr(self.settings, "NICE_NAME"):
       self.nice_name = self.settings.NICE_NAME
       self.nice_name = self.settings.NICE_NAME
     else:
     else:
       self.nice_name = self.name
       self.nice_name = self.name
 
 
+    if self.config_key is not None:
+      self.display_name = self.config_key
+
     # Look for static directory in two places:
     # Look for static directory in two places:
     new_style, old_style = [ os.path.abspath(p) for p in [
     new_style, old_style = [ os.path.abspath(p) for p in [
       os.path.join(module_root, "static"),
       os.path.join(module_root, "static"),
@@ -131,6 +144,7 @@ class DesktopModuleInfo(object):
     s = _import_module_or_none(module_name)
     s = _import_module_or_none(module_name)
     if s is not None:
     if s is not None:
       self.django_apps = getattr(s, 'DJANGO_APPS', [])
       self.django_apps = getattr(s, 'DJANGO_APPS', [])
+      self.config_key = getattr(s, 'CONFIG_KEY', None)
       self.depender_yamls = \
       self.depender_yamls = \
           [self._resolve_appdir_path(p) for p in getattr(s, 'DEPENDER_PACKAGE_YMLS', [])]
           [self._resolve_appdir_path(p) for p in getattr(s, 'DEPENDER_PACKAGE_YMLS', [])]
       self.depender_jsons = \
       self.depender_jsons = \
@@ -138,6 +152,7 @@ class DesktopModuleInfo(object):
            for depname, p in getattr(s, 'DEPENDER_SCRIPTS_JSON', [])]
            for depname, p in getattr(s, 'DEPENDER_SCRIPTS_JSON', [])]
     else:
     else:
       self.django_apps = []
       self.django_apps = []
+      self.config_key = None 
       self.depender_yamls = []
       self.depender_yamls = []
       self.depender_jsons = []
       self.depender_jsons = []
 
 

+ 11 - 3
desktop/core/src/desktop/lib/conf.py

@@ -522,7 +522,7 @@ def _bind_module_members(module, data, section):
   return members
   return members
 
 
 
 
-def bind_module_config(mod, conf_data):
+def bind_module_config(mod, conf_data, config_key):
   """Binds the configuration for the module to the given data.
   """Binds the configuration for the module to the given data.
 
 
   conf_data is a dict-like structure in which the configuration data
   conf_data is a dict-like structure in which the configuration data
@@ -533,6 +533,9 @@ def bind_module_config(mod, conf_data):
     - if the module has a CONFIGURATION_SECTION attribute, that attribute
     - if the module has a CONFIGURATION_SECTION attribute, that attribute
       should be a string, and determines the section name.
       should be a string, and determines the section name.
 
 
+  config_key is the key that should map to the configuration.
+  It's used to allow renaming of configurations.
+
   For example, for the module "hello.world.conf", type(conf_data['hello.world'])
   For example, for the module "hello.world.conf", type(conf_data['hello.world'])
   should be dict-like and contain the configuration for the hello.world
   should be dict-like and contain the configuration for the hello.world
   module.
   module.
@@ -548,7 +551,12 @@ def bind_module_config(mod, conf_data):
   else:
   else:
     section = mod.__name__
     section = mod.__name__
 
 
-  bind_data = conf_data.get(section, {})
+  if config_key is None:
+    bind_data = conf_data.get(section, {})
+  else:
+    section = config_key
+    bind_data = conf_data.get(config_key, {})
+
   members = _bind_module_members(mod, bind_data, section)
   members = _bind_module_members(mod, bind_data, section)
   return ConfigSection(section,
   return ConfigSection(section,
                        members=members,
                        members=members,
@@ -566,7 +574,7 @@ def initialize(modules, config_dir):
   conf_data = load_confs(_configs_from_dir(config_dir))
   conf_data = load_confs(_configs_from_dir(config_dir))
   sections = {}
   sections = {}
   for module in modules:
   for module in modules:
-    section = bind_module_config(module, conf_data)
+    section = bind_module_config(module['module'], conf_data, module['config_key'])
     sections[section.key] = section
     sections[section.key] = section
 
 
   GLOBAL_HELP = "(root of all configuration)"
   GLOBAL_HELP = "(root of all configuration)"

+ 88 - 0
desktop/core/src/desktop/management/commands/config_upgrade.py

@@ -0,0 +1,88 @@
+#!/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.
+"""
+Upgrades a configuration from a mapping file.
+
+Mapping files have a series of search/replace instructions in the form
+s/<old_value>/<new_value>/
+This will rewrite the configurations if any changes are required.
+"""
+import logging
+
+import os, glob, string
+import desktop.conf
+import desktop.log
+from optparse import make_option
+from desktop.lib.paths import get_desktop_root
+from django.core.management.base import BaseCommand, CommandError
+
+LOG = logging.getLogger(__name__)
+
+class Command(BaseCommand):
+  args = ''
+  help = 'Upgrades the Hue configuration with a mapping file.'
+
+  option_list = BaseCommand.option_list + (
+      make_option('--mapping_file', help='Location of the mapping file.'),
+  )
+
+  """Upgrades a configuration."""
+  def handle(self, *args, **options):
+    required = ("mapping_file",)
+    for r in required:
+      if not options.get(r):
+        raise CommandError("--%s is required." % r)
+  
+    # Pull out all the mappings  
+    mapping_file = options["mapping_file"]
+    mapping_handle = open(mapping_file, 'r')
+    mappings = []
+    for mapping in mapping_handle:
+      map_parts = mapping.strip().lstrip('s/')
+      map_parts = map_parts.rstrip('/')
+      map_parts = map_parts.split('/')
+      if len(map_parts) != 2:
+        raise CommandError("Invalid mapping %s in %s" % (mapping.strip(), mapping_file,))
+      mappings.append(map_parts)
+
+    config_dir = os.getenv("HUE_CONF_DIR", get_desktop_root("conf"))
+    for conf_file in glob.glob(os.path.join(config_dir, '*.ini')):
+      LOG.info("Upgrading %s" % conf_file)
+      conf_handle = open(conf_file, 'r')
+      data = []
+      for line in conf_handle:
+        # Pull apart any variables so we don't overwrite config settings
+        data.append(line.split('=', 1))
+
+      # Iterate over mappings to perform
+      for line in data:
+          for mapping in mappings:
+            old_value = mapping[0]
+            new_value = mapping[1]
+
+            if old_value in line[0]:
+              LOG.info("Replacing %s with %s in line %s" % 
+                  (old_value, new_value, '='.join(line),)) 
+              line[0] = line[0].replace(old_value, new_value)
+            
+      
+      # Rewrite file with replacements made
+      conf_handle.close()
+      conf_handle = open(conf_file, 'w')
+      data_to_write = ''.join([ '='.join(split) for split in data ])
+      conf_handle.write(data_to_write)
+

+ 3 - 3
desktop/core/src/desktop/settings.py

@@ -161,7 +161,7 @@ _config_dir = os.getenv("HUE_CONF_DIR", get_desktop_root("conf"))
 
 
 # Libraries are loaded and configured before the apps
 # Libraries are loaded and configured before the apps
 appmanager.load_libs()
 appmanager.load_libs()
-_lib_conf_modules = filter(None, [app.conf for app in appmanager.DESKTOP_LIBS])
+_lib_conf_modules = [dict(module=app.conf, config_key=None) for app in appmanager.DESKTOP_LIBS if app.conf is not None]
 
 
 appmanager.load_apps()
 appmanager.load_apps()
 for app in appmanager.DESKTOP_APPS:
 for app in appmanager.DESKTOP_APPS:
@@ -170,8 +170,8 @@ for app in appmanager.DESKTOP_APPS:
 logging.debug("Installed Django modules: %s" % ",".join(map(str, appmanager.DESKTOP_MODULES)))
 logging.debug("Installed Django modules: %s" % ",".join(map(str, appmanager.DESKTOP_MODULES)))
 
 
 # Load app configuration
 # Load app configuration
-_app_conf_modules = filter(None, [app.conf for app in appmanager.DESKTOP_APPS])
-_app_conf_modules.append(desktop.conf)
+_app_conf_modules = [dict(module=app.conf, config_key=app.config_key) for app in appmanager.DESKTOP_APPS if app.conf is not None]
+_app_conf_modules.append(dict(module=desktop.conf, config_key=None))
 
 
 conf.initialize(_lib_conf_modules, _config_dir)
 conf.initialize(_lib_conf_modules, _config_dir)
 conf.initialize(_app_conf_modules, _config_dir)
 conf.initialize(_app_conf_modules, _config_dir)

+ 1 - 1
desktop/core/src/desktop/templates/dump_config.mako

@@ -28,7 +28,7 @@ from desktop.lib.conf import BoundContainer, is_anonymous
 <ul>
 <ul>
 % for app in sorted(apps, key=lambda app: app.name.lower()):
 % for app in sorted(apps, key=lambda app: app.name.lower()):
   % if hasattr(app, "urls_imported") and app.urls_imported:
   % if hasattr(app, "urls_imported") and app.urls_imported:
-    <li><a href="/${app.name}/">${app.name}</a></li>
+    <li><a href="/${app.name}/">${app.display_name}</a></li>
   % else:
   % else:
     <li>${app.name}</li>
     <li>${app.name}</li>
   % endif
   % endif