diff --git a/devstack/cfg.py b/devstack/cfg.py index 1f2ac03d..d75c0efe 100644 --- a/devstack/cfg.py +++ b/devstack/cfg.py @@ -69,9 +69,9 @@ class IgnoreMissingConfigParser(ConfigParser.RawConfigParser): class StackConfigParser(IgnoreMissingConfigParser): - def __init__(self, cache): + def __init__(self): IgnoreMissingConfigParser.__init__(self) - self.configs_fetched = cache + self.configs_fetched = dict() def _resolve_value(self, section, option, value_gotten): if section == 'host' and option == 'ip': @@ -100,6 +100,12 @@ class StackConfigParser(IgnoreMissingConfigParser): self.configs_fetched[key] = value return value + def set(self, section, option, value): + key = cfg_helpers.make_id(section, option) + LOG.audit("Setting config value [%s] for param [%s]" % (value, key)) + self.configs_fetched[key] = value + IgnoreMissingConfigParser.set(self, section, option, value) + def _resolve_replacements(self, value): LOG.debug("Performing simple replacement on [%s]", value) diff --git a/devstack/components/keystone.py b/devstack/components/keystone.py index 88faae3b..9e04845d 100644 --- a/devstack/components/keystone.py +++ b/devstack/components/keystone.py @@ -261,18 +261,9 @@ def get_shared_params(config, pw_gen, service_user_name=None): mp['DEMO_TENANT_NAME'] = mp['DEMO_USER_NAME'] #tokens and passwords - mp['SERVICE_TOKEN'] = pw_gen.get_password( - "service_token", - 'the service admin token', - ) - mp['ADMIN_PASSWORD'] = pw_gen.get_password( - 'horizon_keystone_admin', - 'the horizon and keystone admin', - 20) - mp['SERVICE_PASSWORD'] = pw_gen.get_password( - 'service_password', - 'service authentication', - ) + mp['SERVICE_TOKEN'] = pw_gen.get_password("service_token") + mp['ADMIN_PASSWORD'] = pw_gen.get_password('horizon_keystone_admin', length=20) + mp['SERVICE_PASSWORD'] = pw_gen.get_password('service_password') #components of the auth endpoint keystone_auth_host = config.getdefaulted('keystone', 'keystone_auth_host', host_ip) diff --git a/devstack/env_rc.py b/devstack/env_rc.py index 2fd14836..37fd5af3 100644 --- a/devstack/env_rc.py +++ b/devstack/env_rc.py @@ -43,6 +43,9 @@ PASSWORDS_MAKES = { 'MYSQL_PASSWORD': 'sql', } +#install root +INSTALL_ROOT = 'INSTALL_ROOT' + #default ports EC2_PORT = 8773 S3_PORT = 3333 @@ -55,9 +58,10 @@ QUOTED_PAT = re.compile(r"^\s*[\"](.*)[\"]\s*$") class RcWriter(object): - def __init__(self, cfg, pw_gen): + def __init__(self, cfg, pw_gen, root_dir): self.cfg = cfg self.pw_gen = pw_gen + self.root_dir = root_dir def _make_export(self, export_name, value): escaped_val = sh.shellquote(value) @@ -93,7 +97,7 @@ class RcWriter(object): def _get_password_envs(self): to_set = dict() for (out_name, key) in PASSWORDS_MAKES.items(): - to_set[out_name] = self.pw_gen.get_password(key, do_prompt=False) + to_set[out_name] = self.pw_gen.extract(key) return to_set def _get_general_envs(self): @@ -101,6 +105,7 @@ class RcWriter(object): for (out_name, cfg_data) in CFG_MAKE.items(): (section, key) = (cfg_data) to_set[out_name] = self.cfg.get(section, key) + to_set[INSTALL_ROOT] = self.root_dir return to_set def _generate_passwords(self): diff --git a/devstack/opts.py b/devstack/opts.py index e1f9eccc..06b3766d 100644 --- a/devstack/opts.py +++ b/devstack/opts.py @@ -19,11 +19,9 @@ from optparse import OptionParser, OptionGroup from devstack import log as logging from devstack import settings -from devstack import shell as sh from devstack import version HELP_WIDTH = 80 -DEF_OS_DIR = "openstack" LOG = logging.getLogger("devstack.opts") @@ -61,16 +59,13 @@ def parse(): dest="action", metavar="ACTION", help="required action to perform: %s" % (_format_list(settings.ACTIONS))) - default_dir = sh.joinpths(sh.gethomedir(), DEF_OS_DIR) base_group.add_option("-d", "--directory", action="store", type="string", dest="dir", metavar="DIR", - default=default_dir, help=("empty root DIR for install or " - "DIR with existing components for start/stop/uninstall " - "(default: %default)")) + "DIR with existing components for start/stop/uninstall")) base_group.add_option("-i", "--ignore-deps", action="store_false", dest="ensure_deps", diff --git a/devstack/passwords.py b/devstack/passwords.py index 3e79694f..83ae90fe 100644 --- a/devstack/passwords.py +++ b/devstack/passwords.py @@ -1,18 +1,34 @@ -#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved. +# Copyright (C) 2012 Dreamhost Inc. All Rights Reserved. +# +# Licensed 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. -import ConfigParser import binascii import getpass import logging import os import re -from devstack import cfg_helpers - LOG = logging.getLogger("devstack.passwords") PW_SECTION = 'passwords' HELPFUL_DESCRIPTIONS = { 'sql': 'the database user', + 'rabbit': 'the rabbit user', + 'horizon_keystone_admin': 'the horizon and keystone admin', + 'service_password': 'service authentication', + "service_token": 'the service admin token', } @@ -29,12 +45,19 @@ def generate_random(length): class PasswordGenerator(object): - def __init__(self, kv_cache, cfg, - prompt_user=True): + def __init__(self, cfg, prompt_user=True): self.cfg = cfg - self.config_cache = kv_cache self.prompt_user = prompt_user + def _valid_password(self, pw): + # FIXME: More efficient way to look for whitespace? + if re.match(r"^(\s+)$", pw) or \ + re.match(r"^(\s+)(\S+)(\s+)$", pw) or \ + re.match(r"^(\S+)(\s+)$", pw) or \ + re.match(r"^(\s+)(\S+)$", pw): + return False + return True + def _prompt_user(self, prompt_text): LOG.debug('Asking the user for a %r password', prompt_text) message = ("Enter a password to use for %s " @@ -43,20 +66,19 @@ class PasswordGenerator(object): rc = "" while True: rc = getpass.getpass(message) - if len(rc) == 0: + if len(rc) == 0 or self._valid_password(rc): break - # FIXME: More efficient way to look for whitespace? - if re.match(r"^(\s+)$", rc): - LOG.warning("Whitespace not allowed as a password!") - elif re.match(r"^(\s+)(\S+)(\s+)$", rc) or \ - re.match(r"^(\S+)(\s+)$", rc) or \ - re.match(r"^(\s+)(\S+)$", rc): - LOG.warning("Whitespace can not start or end a password!") else: - break + LOG.warn("Invalid password \"%s\" (please try again)" % (rc)) return rc - def get_password(self, option, prompt_text=None, length=8, do_prompt=True): + def extract(self, option): + return self.cfg.get(PW_SECTION, option) + + def _set_through(self, option, value): + self.cfg.set(PW_SECTION, option, value) + + def get_password(self, option, prompt_text=None, length=8): """Returns a password identified by the configuration location.""" if not prompt_text: @@ -64,18 +86,16 @@ class PasswordGenerator(object): LOG.debug('Looking for password %s (%s)', option, prompt_text) - cache_key = cfg_helpers.make_id(PW_SECTION, option) - password = self.config_cache.get(cache_key) - # Look in the configuration file(s) + password = None + from_config = False if not password: - try: - password = self.cfg.get(PW_SECTION, option) - except ConfigParser.Error: - password = '' + password = self.cfg.get(PW_SECTION, option) + if password: + from_config = True # Optionally ask the user - if not password and self.prompt_user and do_prompt: + if not password and self.prompt_user: password = self._prompt_user(prompt_text) # If we still don't have a value, make one up. @@ -84,8 +104,8 @@ class PasswordGenerator(object): option, prompt_text) password = generate_random(length) - # Update the cache so that other parts of the - # code can find the value. - self.config_cache[cache_key] = password + # Update via set through to the config + if not from_config: + self._set_through(option, password) return password diff --git a/devstack/progs/actions.py b/devstack/progs/actions.py index ff487b35..07ec56b3 100644 --- a/devstack/progs/actions.py +++ b/devstack/progs/actions.py @@ -164,7 +164,6 @@ class ActionRunner(object): self.force = kargs.get('force', False) self.ignore_deps = kargs.get('ignore_deps', False) self.ref_components = kargs.get("ref_components") - self.rc_file = sh.abspth(settings.OSRC_FN) self.gen_rc = action in _RC_FILE_MAKE_ACTIONS def _get_components(self): @@ -233,13 +232,6 @@ class ActionRunner(object): def _pre_run(self, instances, component_order): if not sh.isdir(self.directory): sh.mkdir(self.directory) - if self.rc_file: - try: - LOG.info("Attempting to load rc file at [%s] which has your environment settings." % (self.rc_file)) - am_loaded = env_rc.RcReader().load(self.rc_file) - LOG.info("Loaded [%s] settings from rc file [%s]" % (am_loaded, self.rc_file)) - except IOError: - LOG.warn('Error reading rc file located at [%s]. Skipping loading it.' % (self.rc_file)) LOG.info("Verifying that the components are ready to rock-n-roll.") for component in component_order: inst = instances[component] @@ -248,15 +240,15 @@ class ActionRunner(object): for component in component_order: inst = instances[component] inst.warm_configs() - if self.gen_rc and self.rc_file: - writer = env_rc.RcWriter(self.cfg, self.pw_gen) - if not sh.isfile(self.rc_file): - LOG.info("Generating a file at [%s] that will contain your environment settings." % (self.rc_file)) - writer.write(self.rc_file) + if self.gen_rc: + writer = env_rc.RcWriter(self.cfg, self.pw_gen, self.directory) + if not sh.isfile(settings.OSRC_FN): + LOG.info("Generating a file at [%s] that will contain your environment settings." % (settings.OSRC_FN)) + writer.write(settings.OSRC_FN) else: - LOG.info("Updating a file at [%s] that contains your environment settings." % (self.rc_file)) - am_upd = writer.update(self.rc_file) - LOG.info("Updated [%s] settings in rc file [%s]" % (am_upd, self.rc_file)) + LOG.info("Updating a file at [%s] that contains your environment settings." % (settings.OSRC_FN)) + am_upd = writer.update(settings.OSRC_FN) + LOG.info("Updated [%s] settings in rc file [%s]" % (am_upd, settings.OSRC_FN)) def _run_instances(self, instances, component_order): component_order = self._apply_reverse(component_order) diff --git a/devstack/progs/common.py b/devstack/progs/common.py index 799679b2..54f15e79 100644 --- a/devstack/progs/common.py +++ b/devstack/progs/common.py @@ -155,12 +155,10 @@ def get_packager(distro, keep_packages): return cls(distro, keep_packages) -def get_config(cfg_fn=None, kv_cache=None): +def get_config(cfg_fn=None): if not cfg_fn: cfg_fn = sh.canon_path(settings.STACK_CONFIG_LOCATION) - if kv_cache is None: - kv_cache = dict() - config_instance = cfg.StackConfigParser(kv_cache) + config_instance = cfg.StackConfigParser() config_instance.read(cfg_fn) return config_instance diff --git a/devstack/utils.py b/devstack/utils.py index 2ec921de..f8c8e9a3 100644 --- a/devstack/utils.py +++ b/devstack/utils.py @@ -88,9 +88,9 @@ def configure_logging(verbosity_level=1, dry_run=False): # Adjust logging verbose level based on the command line switch. log_level = logging.INFO - if verbosity_level >= 2: + if verbosity_level >= 3: log_level = logging.DEBUG - elif dry_run: + elif verbosity_level == 2 or dry_run: log_level = logging.AUDIT root_logger.setLevel(log_level) diff --git a/stack b/stack index c3ea7639..82a643cd 100755 --- a/stack +++ b/stack @@ -22,6 +22,8 @@ import traceback from devstack import cfg_helpers from devstack import date +from devstack import env +from devstack import env_rc from devstack import log as logging from devstack import opts from devstack import passwords @@ -87,19 +89,39 @@ def dump_config(config_cache): map_print(entries) +def load_rc_files(): + fns = [settings.OSRC_FN] + for fn in fns: + try: + LOG.debug("Attempting to load rc file at [%s] which has your environment settings." % (fn)) + am_loaded = env_rc.RcReader().load(fn) + LOG.debug("Loaded [%s] settings from rc file [%s]" % (am_loaded, fn)) + except IOError: + LOG.warn('Error reading rc file located at [%s]. Skipping loading it.' % (fn)) + return len(fns) + + def run(args): + (distro, platform) = utils.determine_distro() if distro is None: print("Unsupported platform " + utils.color_text(platform, "red") + "!") return False + action = args.pop("action").strip().lower() if not (action in settings.ACTIONS): print(utils.color_text("No valid action specified!", "red")) return False + + loaded_rcs = False rootdir = args.pop("dir") if not rootdir: - print(utils.color_text("No root directory specified!", "red")) - return False + load_rc_files() + loaded_rcs = True + rootdir = env.get_key(env_rc.INSTALL_ROOT) + if not rootdir: + print(utils.color_text("No root directory specified!", "red")) + return False #welcome! (repeat_string, line_max_len) = utils.welcome(_WELCOME_MAP.get(action)) @@ -108,12 +130,16 @@ def run(args): #here on out we should be using the logger (and not print) start_time = time.time() + # if we didn't load them before, load them now + if not loaded_rcs: + load_rc_files() + loaded_rcs = True + # Stash the dryrun value (if any) into the global configuration sh.set_dryrun(args['dryrun']) - config_kv_cache = dict() - config = common.get_config(kv_cache=config_kv_cache) - pw_gen = passwords.PasswordGenerator(config_kv_cache, config, args['prompt_for_passwords']) + config = common.get_config() + pw_gen = passwords.PasswordGenerator(config, args['prompt_for_passwords']) pkg_manager = common.get_packager(distro, args['keep_old']) components = utils.parse_components(args.pop("components")) @@ -125,7 +151,7 @@ def run(args): LOG.info("It took (%s) to complete action [%s]" % (common.format_secs_taken((time.time() - start_time)), action)) LOG.info("After action [%s] your settings which were created or read are:" % (action)) - dump_config(config_kv_cache) + dump_config(config.configs_fetched) return True