Move the password generation logic to its own class and out of the config

parser to cut down on the number of places that have component-specific
configuration knowledge.

Add a --no-prompt-passwords flag to stack for users that want
auto-generated passwords without having to press enter for each
one.

Pork:
- Ignore the emacs TAGS file.
This commit is contained in:
Doug Hellmann 2012-03-12 14:08:19 -04:00
parent 86041e8c50
commit 0018c7cccf
16 changed files with 203 additions and 115 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ pidfile
*.komodoproject
.coverage
.DS_Store
TAGS

View File

@ -30,16 +30,6 @@ PW_TMPL = "Enter a password for %s: "
ENV_PAT = re.compile(r"^\s*\$\{([\w\d]+):\-(.*)\}\s*$")
SUB_MATCH = re.compile(r"(?:\$\(([\w\d]+):([\w\d]+))\)")
CACHE_MSG = "(value will now be internally cached)"
PW_SECTIONS = ['passwords']
DEF_PW_MSG = "[or press enter to get a generated one]"
PW_PROMPTS = {
'horizon_keystone_admin': "Enter a password to use for horizon and keystone (20 chars or less) %s: " % (DEF_PW_MSG),
'service_token': 'Enter a token to use for the service admin token %s: ' % (DEF_PW_MSG),
'sql': 'Enter a password to use for your sql database user %s: ' % (DEF_PW_MSG),
'rabbit': 'Enter a password to use for your rabbit user %s: ' % (DEF_PW_MSG),
'old_sql': "Please enter your current mysql password so we that can reset it for next time: ",
'service_password': "Enter a service password to use for the service authentication %s:" % (DEF_PW_MSG),
}
class IgnoreMissingConfigParser(ConfigParser.RawConfigParser):
@ -91,56 +81,54 @@ def make_id(section, option):
class StackConfigParser(IgnoreMissingConfigParser):
def __init__(self):
IgnoreMissingConfigParser.__init__(self)
self.pws = dict()
self.configs_fetched = dict()
self.db_dsns = dict()
def _resolve_value(self, section, option, value_gotten, auto_pw):
def _resolve_value(self, section, option, value_gotten):
key = make_id(section, option)
if section in PW_SECTIONS and key not in self.pws and value_gotten:
self.pws[key] = value_gotten
if section == 'host' and option == 'ip':
LOG.debug("Host ip from configuration/environment was empty, programatically attempting to determine it.")
value_gotten = utils.get_host_ip()
LOG.debug("Determined your host ip to be: [%s]" % (value_gotten))
if section in PW_SECTIONS and auto_pw and not value_gotten:
LOG.debug("Being forced to ask for password for [%s] since the configuration value is empty.", key)
value_gotten = sh.password(PW_PROMPTS.get(option, PW_TMPL % (key)))
self.pws[key] = value_gotten
return value_gotten
def getdefaulted(self, section, option, default_val, auto_pw=True):
val = self.get(section, option, auto_pw)
def getdefaulted(self, section, option, default_val):
val = self.get(section, option)
if not val or not val.strip():
LOG.debug("Value [%s] found was not good enough, returning provided default [%s]" % (val, default_val))
return default_val
return val
def get(self, section, option, auto_pw=True):
def get(self, section, option):
key = make_id(section, option)
if key in self.configs_fetched:
value = self.configs_fetched.get(key)
LOG.debug("Fetched cached value [%s] for param [%s]" % (value, key))
else:
LOG.debug("Fetching value for param [%s]" % (key))
gotten_value = self._get_bashed(section, option, auto_pw)
value = self._resolve_value(section, option, gotten_value, auto_pw)
gotten_value = self._get_bashed(section, option)
value = self._resolve_value(section, option, gotten_value)
LOG.debug("Fetched [%s] for [%s] %s" % (value, key, CACHE_MSG))
self.configs_fetched[key] = value
return value
def _resolve_replacements(self, value, auto_pw):
def set(self, section, option, value):
key = make_id(section, option)
self.configs_fetched[key] = value
return IgnoreMissingConfigParser.set(self, section, option, value)
def _resolve_replacements(self, value):
LOG.debug("Performing simple replacement on [%s]", value)
#allow for our simple replacement to occur
def replacer(match):
section = match.group(1)
option = match.group(2)
return self.getdefaulted(section, option, '', auto_pw)
return self.getdefaulted(section, option, '')
return SUB_MATCH.sub(replacer, value)
def _get_bashed(self, section, option, auto_pw):
def _get_bashed(self, section, option):
value = IgnoreMissingConfigParser.get(self, section, option)
if value is None:
return value
@ -155,7 +143,7 @@ class StackConfigParser(IgnoreMissingConfigParser):
env_value = env.get_key(env_key)
if env_value is None:
LOG.debug("Extracting value from config provided default value [%s]" % (def_val))
extracted_val = self._resolve_replacements(def_val, auto_pw)
extracted_val = self._resolve_replacements(def_val)
LOG.debug("Using config provided default value [%s] (no environment key)" % (extracted_val))
else:
extracted_val = env_value

View File

@ -54,6 +54,7 @@ class ComponentBase(object):
def __init__(self, component_name, **kargs):
self.component_name = component_name
self.cfg = kargs.get("config")
self.password_generator = kargs.get('password_generator')
self.packager = kargs.get("packager")
self.distro = kargs.get("distro")
self.instances = kargs.get("instances") or dict()

View File

@ -85,6 +85,8 @@ REQ_PKGS = ['db.json']
#config keys we warm up so u won't be prompted later
WARMUP_PWS = ['sql']
PASSWORD_DESCRIPTION = 'the database user'
class DBUninstaller(comp.PkgUninstallComponent):
def __init__(self, *args, **kargs):
@ -93,7 +95,8 @@ class DBUninstaller(comp.PkgUninstallComponent):
def warm_configs(self):
for pw_key in WARMUP_PWS:
self.cfg.get("passwords", pw_key)
self.password_generator.get_password(
'passwords', pw_key, PASSWORD_DESCRIPTION)
def pre_uninstall(self):
dbtype = self.cfg.get("db", "type")
@ -106,8 +109,10 @@ class DBUninstaller(comp.PkgUninstallComponent):
if pwd_cmd:
LOG.info("Ensuring your database is started before we operate on it.")
self.runtime.restart()
old_pw = self.password_generator.get_password(
'passwords', 'sql', PASSWORD_DESCRIPTION)
params = {
'OLD_PASSWORD': self.cfg.get("passwords", 'sql'),
'OLD_PASSWORD': old_pw,
'NEW_PASSWORD': RESET_BASE_PW,
'USER': self.cfg.getdefaulted("db", "sql_user", 'root'),
}
@ -129,7 +134,8 @@ class DBInstaller(comp.PkgInstallComponent):
#in pre-install and post-install sections
host_ip = self.cfg.get('host', 'ip')
out = {
'PASSWORD': self.cfg.get("passwords", "sql"),
'PASSWORD': self.password_generator.get_password(
"passwords", "sql", PASSWORD_DESCRIPTION),
'BOOT_START': ("%s" % (True)).lower(),
'USER': self.cfg.getdefaulted("db", "sql_user", 'root'),
'SERVICE_HOST': host_ip,
@ -139,7 +145,8 @@ class DBInstaller(comp.PkgInstallComponent):
def warm_configs(self):
for pw_key in WARMUP_PWS:
self.cfg.get("passwords", pw_key)
self.password_generator.get_password(
'passwords', pw_key, PASSWORD_DESCRIPTION)
def _configure_db_confs(self):
dbtype = self.cfg.get("db", "type")
@ -192,7 +199,8 @@ class DBInstaller(comp.PkgInstallComponent):
LOG.info("Ensuring your database is started before we operate on it.")
self.runtime.restart()
params = {
'NEW_PASSWORD': self.cfg.get("passwords", "sql"),
'NEW_PASSWORD': self.password_generator.get_password(
"passwords", "sql", PASSWORD_DESCRIPTION),
'USER': self.cfg.getdefaulted("db", "sql_user", 'root'),
'OLD_PASSWORD': RESET_BASE_PW,
}
@ -211,13 +219,11 @@ class DBInstaller(comp.PkgInstallComponent):
LOG.info("Ensuring your database is started before we operate on it.")
self.runtime.restart()
params = {
'PASSWORD': self.cfg.get("passwords", "sql"),
'PASSWORD': self.password_generator.get_password(
"passwords", "sql", PASSWORD_DESCRIPTION),
'USER': user,
}
cmds = list()
cmds.append({
'cmd': grant_cmd,
})
cmds = [{'cmd': grant_cmd}]
#shell seems to be needed here
#since python escapes this to much...
utils.execute_template(*cmds, params=params, shell=True)

View File

@ -187,7 +187,7 @@ class GlanceInstaller(comp.PythonInstallComponent):
mp['SQL_CONN'] = self.cfg.get_dbdsn(DB_NAME)
mp['SERVICE_HOST'] = self.cfg.get('host', 'ip')
mp['HOST_IP'] = self.cfg.get('host', 'ip')
mp.update(keystone.get_shared_params(self.cfg, 'glance'))
mp.update(keystone.get_shared_params(self.cfg, self.password_generator, 'glance'))
return mp
@ -224,4 +224,4 @@ class GlanceRuntime(comp.PythonRuntime):
# TODO: make this less cheesy - need to wait till glance goes online
LOG.info("Waiting %s seconds so that glance can start up before image install." % (WAIT_ONLINE_TO))
sh.sleep(WAIT_ONLINE_TO)
creator.ImageCreationService(self.cfg).install()
creator.ImageCreationService(self.cfg, self.password_generator).install()

View File

@ -191,7 +191,7 @@ class KeystoneInstaller(comp.PythonInstallComponent):
return comp.PythonInstallComponent._get_source_config(self, config_fn)
def warm_configs(self):
get_shared_params(self.cfg)
get_shared_params(self.cfg, self.password_generator)
def _get_param_map(self, config_fn):
#these be used to fill in the configuration/cmds +
@ -204,9 +204,9 @@ class KeystoneInstaller(comp.PythonInstallComponent):
if config_fn == ROOT_CONF:
mp['SQL_CONN'] = self.cfg.get_dbdsn(DB_NAME)
mp['KEYSTONE_DIR'] = self.appdir
mp.update(get_shared_params(self.cfg))
mp.update(get_shared_params(self.cfg, self.password_generator))
elif config_fn == MANAGE_DATA_CONF:
mp.update(get_shared_params(self.cfg))
mp.update(get_shared_params(self.cfg, self.password_generator))
return mp
@ -248,7 +248,8 @@ class KeystoneRuntime(comp.PythonRuntime):
return APP_OPTIONS.get(app)
def get_shared_params(config, service_user_name=None):
def get_shared_params(config, password_generator, service_user_name=None):
LOG.debug('password_generator %s', password_generator)
mp = dict()
host_ip = config.get('host', 'ip')
@ -262,9 +263,21 @@ def get_shared_params(config, service_user_name=None):
mp['DEMO_TENANT_NAME'] = mp['DEMO_USER_NAME']
#tokens and passwords
mp['SERVICE_TOKEN'] = config.get("passwords", "service_token")
mp['ADMIN_PASSWORD'] = config.get('passwords', 'horizon_keystone_admin')
mp['SERVICE_PASSWORD'] = config.get('passwords', 'service_password')
mp['SERVICE_TOKEN'] = password_generator.get_password(
'passwords',
"service_token",
'the service admin token',
)
mp['ADMIN_PASSWORD'] = password_generator.get_password(
'passwords',
'horizon_keystone_admin',
'the horizon and keystone admin',
20)
mp['SERVICE_PASSWORD'] = password_generator.get_password(
'passwords',
'service_password',
'service authentication',
)
#components of the auth endpoint
keystone_auth_host = config.getdefaulted('keystone', 'keystone_auth_host', host_ip)

View File

@ -408,7 +408,7 @@ class NovaInstaller(comp.PythonInstallComponent):
mp['FIXED_NETWORK_SIZE'] = self.cfg.getdefaulted('nova', 'fixed_network_size', '256')
mp['FIXED_RANGE'] = self.cfg.getdefaulted('nova', 'fixed_range', '10.0.0.0/24')
else:
mp.update(keystone.get_shared_params(self.cfg, 'nova'))
mp.update(keystone.get_shared_params(self.cfg, self.password_generator, 'nova'))
return mp
def configure(self):

View File

@ -68,12 +68,12 @@ class RabbitInstaller(comp.PkgInstallComponent):
def warm_configs(self):
for pw_key in WARMUP_PWS:
self.cfg.get("passwords", pw_key)
self.password_generator.get_password("passwords", pw_key, 'the rabbit user')
def _setup_pw(self):
LOG.info("Setting up your rabbit-mq guest password.")
self.runtime.restart()
passwd = self.cfg.get("passwords", "rabbit")
passwd = self.password_generator.get_password('passwords', "rabbit", 'the rabbit user')
cmd = PWD_CMD + [passwd]
sh.execute(*cmd, run_as_root=True)
LOG.info("Restarting so that your rabbit-mq guest password is reflected.")

View File

@ -19,12 +19,15 @@ import re
from devstack import date
from devstack import env
from devstack import log as logging
from devstack import settings
from devstack import shell as sh
from devstack import utils
from devstack.components import keystone
LOG = logging.getLogger('devstack.env_rc')
#general extraction cfg keys
CFG_MAKE = {
'ADMIN_PASSWORD': ('passwords', 'horizon_keystone_admin'),
@ -48,8 +51,9 @@ QUOTED_PAT = re.compile(r"^\s*[\"](.*)[\"]\s*$")
class RcWriter(object):
def __init__(self, cfg):
def __init__(self, cfg, password_generator):
self.cfg = cfg
self.password_generator = password_generator
def _make_export(self, export_name, value):
escaped_val = sh.shellquote(value)
@ -68,11 +72,11 @@ class RcWriter(object):
to_set = dict()
ip = self.cfg.get('host', 'ip')
ec2_url_default = urlunparse(('http', "%s:%s" % (ip, EC2_PORT), "services/Cloud", '', '', ''))
to_set['EC2_URL'] = self.cfg.getdefaulted('extern', 'ec2_url', ec2_url_default, auto_pw=False)
to_set['EC2_URL'] = self.cfg.getdefaulted('extern', 'ec2_url', ec2_url_default)
s3_url_default = urlunparse(('http', "%s:%s" % (ip, S3_PORT), "services/Cloud", '', '', ''))
to_set['S3_URL'] = self.cfg.getdefaulted('extern', 's3_url', s3_url_default, auto_pw=False)
to_set['EC2_CERT'] = self.cfg.get('extern', 'ec2_cert_fn', auto_pw=False)
to_set['EC2_USER_ID'] = self.cfg.get('extern', 'ec2_user_id', auto_pw=False)
to_set['S3_URL'] = self.cfg.getdefaulted('extern', 's3_url', s3_url_default)
to_set['EC2_CERT'] = self.cfg.get('extern', 'ec2_cert_fn')
to_set['EC2_USER_ID'] = self.cfg.get('extern', 'ec2_user_id')
return to_set
def _generate_ec2_env(self):
@ -86,7 +90,7 @@ class RcWriter(object):
to_set = dict()
for (out_name, cfg_data) in CFG_MAKE.items():
(section, key) = (cfg_data)
to_set[out_name] = self.cfg.get(section, key, auto_pw=False)
to_set[out_name] = self.cfg.get(section, key)
return to_set
def _generate_general(self):
@ -149,7 +153,7 @@ class RcWriter(object):
sh.write_file(fn, contents)
def _get_os_envs(self):
key_params = keystone.get_shared_params(self.cfg)
key_params = keystone.get_shared_params(self.cfg, self.password_generator)
to_set = dict()
to_set['OS_PASSWORD'] = key_params['ADMIN_PASSWORD']
to_set['OS_TENANT_NAME'] = key_params['DEMO_TENANT_NAME']
@ -179,7 +183,7 @@ alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_
def _get_euca_envs(self):
to_set = dict()
to_set['EUCALYPTUS_CERT'] = self.cfg.get('extern', 'nova_cert_fn', auto_pw=False)
to_set['EUCALYPTUS_CERT'] = self.cfg.get('extern', 'nova_cert_fn')
return to_set
def _generate_euca_env(self):
@ -191,8 +195,8 @@ alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_
def _get_nova_envs(self):
to_set = dict()
to_set['NOVA_VERSION'] = self.cfg.get('nova', 'nova_version', auto_pw=False)
to_set['NOVA_CERT'] = self.cfg.get('extern', 'nova_cert_fn', auto_pw=False)
to_set['NOVA_VERSION'] = self.cfg.get('nova', 'nova_version')
to_set['NOVA_CERT'] = self.cfg.get('extern', 'nova_cert_fn')
return to_set
def _generate_nova_env(self):

View File

@ -211,13 +211,14 @@ class ImageRegistry:
class ImageCreationService:
def __init__(self, cfg):
def __init__(self, cfg, password_generator):
self.cfg = cfg
self.password_generator = password_generator
def _get_token(self):
LOG.info("Fetching your keystone admin token so that we can perform image uploads.")
key_params = keystone.get_shared_params(self.cfg)
key_params = keystone.get_shared_params(self.cfg, self.password_generator)
keystone_service_url = key_params['SERVICE_ENDPOINT']
keystone_token_url = "%s/tokens" % (keystone_service_url)

View File

@ -77,6 +77,12 @@ def parse():
action="store_false",
dest="ensure_deps",
help="ignore dependencies when performing ACTION")
base_group.add_option("--no-prompt-passwords",
action="store_false",
dest="prompt_for_passwords",
default=True,
help="do not prompt the user for passwords",
)
base_group.add_option("-e", "--ensure-deps",
action="store_true",
dest="ensure_deps",
@ -118,5 +124,6 @@ def parse():
output['keep_old'] = options.keep_old
output['extras'] = args
output['verbosity'] = len(options.verbosity)
output['prompt_for_passwords'] = options.prompt_for_passwords
return output

78
devstack/passwords.py Normal file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python
import binascii
import ConfigParser
import getpass
import logging
import os
import re
from devstack.cfg import make_id
LOG = logging.getLogger("devstack.passwords")
def generate_random(length):
"""Returns a randomly generated password of the specified length."""
LOG.debug("Generating a pseudo-random password of %d characters",
length)
return binascii.hexlify(os.urandom((length + 1) / 2))[:length]
class PasswordGenerator(object):
def __init__(self, cfg, prompt_user=True):
self.cfg = cfg
self.prompt_user = prompt_user
# Store the values accessed by the caller
# so the main script can print them out
# at the end.
self.accessed = {}
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 "
"[or press enter to get a generated one] " % prompt_text
)
rc = ""
while True:
rc = getpass.getpass(message)
if len(rc) == 0:
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
return rc
# FIXME: Remove the "section" argument, since it is always the same.
def get_password(self, section, option, prompt_text, length=8):
"""Returns a password identified by the configuration location."""
LOG.debug('looking for password %s (%s)', option, prompt_text)
# Look in the configuration file(s)
try:
password = self.cfg.get(section, option)
except ConfigParser.Error:
password = ''
# Optionally ask the user
if not password and self.prompt_user:
password = self._prompt_user(prompt_text)
self.accessed[make_id(section, option)] = password
# If we still don't have a value, make one up.
if not password:
LOG.debug('no configured password for %s (%s)',
option, prompt_text)
password = generate_random(length)
# Update the configration cache so that other parts of the
# code can find the value.
self.cfg.set(section, option, password)
return password

View File

@ -31,7 +31,8 @@ _REVERSE_ACTIONS = [settings.UNINSTALL, settings.STOP]
# For these actions we will attempt to make an rc file if it does not exist
_RC_FILE_MAKE_ACTIONS = [settings.INSTALL]
# The order of which uninstalls happen + message of what is happening (before and after)
# The order of which uninstalls happen + message of what is happening
# (before and after)
UNINSTALL_ORDERING = [
(
"Unconfiguring {name}.",
@ -55,7 +56,8 @@ UNINSTALL_ORDERING = [
),
]
# The order of which starts happen + message of what is happening (before and after)
# The order of which starts happen + message of what is happening
# (before and after)
STARTS_ORDERING = [
(
"Configuring runner for {name}.",
@ -79,7 +81,8 @@ STARTS_ORDERING = [
),
]
# The order of which stops happen + message of what is happening (before and after)
# The order of which stops happen + message of what is happening
# (before and after)
STOPS_ORDERING = [
(
"Stopping {name}.",
@ -88,7 +91,8 @@ STOPS_ORDERING = [
),
]
# The order of which install happen + message of what is happening (before and after)
# The order of which install happen + message of what is happening
# (before and after)
INSTALL_ORDERING = [
(
"Downloading {name}.",
@ -125,7 +129,8 @@ ACTION_MP = {
settings.UNINSTALL: UNINSTALL_ORDERING,
}
# These actions must have there prerequisite action accomplished (if determined by the boolean lambda to be needed)
# These actions must have there prerequisite action accomplished (if
# determined by the boolean lambda to be needed)
PREQ_ACTIONS = {
settings.START: ((lambda instance: (not instance.is_installed())), settings.INSTALL),
settings.UNINSTALL: ((lambda instance: (instance.is_started())), settings.STOP),
@ -133,11 +138,14 @@ PREQ_ACTIONS = {
class ActionRunner(object):
def __init__(self, distro, action, directory, config, pkg_manager, **kargs):
def __init__(self, distro, action, directory, config,
password_generator, pkg_manager,
**kargs):
self.distro = distro
self.action = action
self.directory = directory
self.cfg = config
self.password_generator = password_generator
self.pkg_manager = pkg_manager
self.kargs = kargs
self.components = dict()
@ -186,14 +194,18 @@ class ActionRunner(object):
all_instances = dict()
for component in components.keys():
cls = common.get_action_cls(self.action, component, self.distro)
# FIXME: Instead of passing some of these options,
# pass a reference to the runner itself and let
# the component keep a weakref to it.
instance = cls(instances=all_instances,
distro=self.distro,
packager=self.pkg_manager,
config=self.cfg,
root=self.directory,
opts=components.get(component, list()),
keep_old=self.kargs.get("keep_old")
)
distro=self.distro,
packager=self.pkg_manager,
config=self.cfg,
password_generator=self.password_generator,
root=self.directory,
opts=components.get(component, list()),
keep_old=self.kargs.get("keep_old")
)
all_instances[component] = instance
return all_instances
@ -233,7 +245,7 @@ class ActionRunner(object):
inst = instances[component]
inst.warm_configs()
if self.gen_rc and self.rc_file:
writer = env_rc.RcWriter(self.cfg)
writer = env_rc.RcWriter(self.cfg, self.password_generator)
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)

View File

@ -22,8 +22,6 @@ import pwd
import shutil
import subprocess
import sys
import random
import re
import time
from devstack import env
@ -45,7 +43,6 @@ SHELL_QUOTE_REPLACERS = {
}
SHELL_WRAPPER = "\"%s\""
ROOT_PATH = os.sep
RANDOMIZER = random.SystemRandom()
DRYRUN = None
@ -239,32 +236,6 @@ def _get_suids():
return (uid, gid)
def _gen_password(pw_len):
if pw_len <= 0:
msg = "Password length %s can not be less than or equal to zero" % (pw_len)
raise excp.BadParamException(msg)
LOG.debug("Generating you a pseudo-random password of byte length: %s" % (pw_len))
random_num = RANDOMIZER.getrandbits(pw_len * 8)
pw = "%x" % (random_num)
LOG.debug("Generated you a pseudo-random password [%s]" % (pw))
return pw
def prompt_password(pw_prompt):
rc = ""
while True:
rc = getpass.getpass(pw_prompt)
if len(rc) == 0:
break
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
return rc
def chown_r(path, uid, gid, run_as_root=True):
@ -282,13 +253,6 @@ def chown_r(path, uid, gid, run_as_root=True):
LOG.debug("Changing ownership of %s to %s:%s" % (joinpths(root, f), uid, gid))
def password(pw_prompt, pw_len=8):
pw = prompt_password(pw_prompt)
if len(pw) == 0:
return _gen_password(pw_len)
else:
return pw
def _explode_path(path):
parts = list()

11
stack
View File

@ -26,6 +26,7 @@ from devstack import colorlog
from devstack import date
from devstack import log as lg
from devstack import opts
from devstack import passwords
from devstack import settings
from devstack import shell as sh
from devstack import utils
@ -45,7 +46,7 @@ _WELCOME_MAP = {
}
def dump_config(config_obj):
def dump_config(config_obj, password_generator):
def item_format(key, value):
return "\t%s=%s" % (str(key), str(value))
@ -55,7 +56,7 @@ def dump_config(config_obj):
value = mp.get(key)
LOG.info(item_format(key, value))
passwords_gotten = config_obj.pws
passwords_gotten = password_generator.accessed
full_cfgs = config_obj.configs_fetched
db_dsns = config_obj.db_dsns
if passwords_gotten or full_cfgs or db_dsns:
@ -97,14 +98,16 @@ def run(args):
dryrun = args.get('dryrun')
if dryrun:
sh.set_dryrun(dryrun)
password_generator = passwords.PasswordGenerator(config, args['prompt_for_passwords'])
pkg_manager = common.get_packager(distro, args.get('keep_old'))
components = utils.parse_components(args.pop("components"))
runner = actions.ActionRunner(distro, action, rootdir, config, pkg_manager, components=components, **args)
runner = actions.ActionRunner(distro, action, rootdir, config, password_generator,
pkg_manager, components=components, **args)
LOG.info("Starting action [%s] on %s for distro [%s]" % (action, date.rcf8222date(), distro))
runner.run()
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)
dump_config(config, password_generator)
return True

10
tests/test_passwords.py Normal file
View File

@ -0,0 +1,10 @@
from devstack import passwords
def test_generate_random():
def check_one(i):
p = passwords.generate_random(i)
assert len(p) == i
for i in range(1, 9):
yield check_one, i