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:
parent
86041e8c50
commit
0018c7cccf
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ pidfile
|
||||
*.komodoproject
|
||||
.coverage
|
||||
.DS_Store
|
||||
TAGS
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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.")
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
78
devstack/passwords.py
Normal 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
|
@ -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)
|
||||
|
@ -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
11
stack
@ -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
10
tests/test_passwords.py
Normal 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
|
Loading…
Reference in New Issue
Block a user