Generate undercloud-passwords.conf and fix output dir.
CI depends on undercloud-passwords.conf and its existance in its home directory. This generates the passwords file in the same format as instack and also outputs all state to the same directory. Co-Authored-By: Steven Hardy <shardy@redhat.com> Change-Id: I1c60fd2f2732acb343a2f128258dbf0141ba0c82
This commit is contained in:
@@ -74,7 +74,8 @@ class TestUndercloudInstall(TestPluginV1):
|
|||||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||||
'config-download-environment.yaml', '-e',
|
'config-download-environment.yaml', '-e',
|
||||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||||
'undercloud.yaml', '-e', '/tmp/undercloud_parameters.yaml'])
|
'undercloud.yaml', '-e', '/tmp/undercloud_parameters.yaml',
|
||||||
|
mock.ANY])
|
||||||
|
|
||||||
|
|
||||||
class TestUndercloudUpgrade(TestPluginV1):
|
class TestUndercloudUpgrade(TestPluginV1):
|
||||||
@@ -135,4 +136,5 @@ class TestUndercloudUpgrade(TestPluginV1):
|
|||||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||||
'config-download-environment.yaml', '-e',
|
'config-download-environment.yaml', '-e',
|
||||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||||
'undercloud.yaml', '-e', '/tmp/undercloud_parameters.yaml'])
|
'undercloud.yaml', '-e', '/tmp/undercloud_parameters.yaml',
|
||||||
|
mock.ANY])
|
||||||
|
@@ -38,50 +38,65 @@ class TestUndercloudDeploy(TestPluginV1):
|
|||||||
# Substitute required packages
|
# Substitute required packages
|
||||||
self.cmd.prerequisites = iter(['foo', 'bar', 'baz'])
|
self.cmd.prerequisites = iter(['foo', 'bar', 'baz'])
|
||||||
|
|
||||||
|
@mock.patch('os.chmod')
|
||||||
@mock.patch('os.path.exists')
|
@mock.patch('os.path.exists')
|
||||||
@mock.patch('tripleo_common.utils.passwords.generate_passwords')
|
@mock.patch('tripleo_common.utils.passwords.generate_passwords')
|
||||||
@mock.patch('yaml.safe_dump')
|
@mock.patch('yaml.safe_dump')
|
||||||
def test_update_passwords_env_init(self, mock_dump, mock_pw, mock_exists):
|
def test_update_passwords_env_init(self, mock_dump, mock_pw,
|
||||||
|
mock_exists, mock_chmod):
|
||||||
pw_dict = {"GeneratedPassword": 123}
|
pw_dict = {"GeneratedPassword": 123}
|
||||||
pw_env_path = os.path.join(self.temp_homedir,
|
pw_conf_path = os.path.join(self.temp_homedir,
|
||||||
'tripleo-undercloud-passwords.yaml')
|
'undercloud-passwords.conf')
|
||||||
|
t_pw_conf_path = os.path.join(
|
||||||
|
self.temp_homedir, 'tripleo-undercloud-passwords.yaml')
|
||||||
|
|
||||||
mock_pw.return_value = pw_dict
|
mock_pw.return_value = pw_dict
|
||||||
mock_exists.return_value = False
|
mock_exists.return_value = False
|
||||||
|
|
||||||
mock_open_context = mock.mock_open()
|
mock_open_context = mock.mock_open()
|
||||||
with mock.patch('six.moves.builtins.open', mock_open_context):
|
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||||
self.cmd._update_passwords_env()
|
self.cmd._update_passwords_env(self.temp_homedir)
|
||||||
|
|
||||||
mock_exists.assert_called_once_with(pw_env_path)
|
mock_open_context.assert_called_with(pw_conf_path, 'w')
|
||||||
mock_open_context.assert_called_once_with(pw_env_path, 'w')
|
|
||||||
mock_open_handle = mock_open_context()
|
mock_open_handle = mock_open_context()
|
||||||
mock_dump.assert_called_once_with({'parameter_defaults': pw_dict},
|
mock_dump.assert_called_once_with({'parameter_defaults': pw_dict},
|
||||||
mock_open_handle,
|
mock_open_handle,
|
||||||
default_flow_style=False)
|
default_flow_style=False)
|
||||||
|
chmod_calls = [mock.call(t_pw_conf_path, 0o600),
|
||||||
|
mock.call(pw_conf_path, 0o600)]
|
||||||
|
mock_chmod.assert_has_calls(chmod_calls)
|
||||||
|
|
||||||
|
@mock.patch('os.chmod')
|
||||||
@mock.patch('os.path.exists')
|
@mock.patch('os.path.exists')
|
||||||
@mock.patch('tripleo_common.utils.passwords.generate_passwords')
|
@mock.patch('tripleo_common.utils.passwords.generate_passwords')
|
||||||
@mock.patch('yaml.safe_dump')
|
@mock.patch('yaml.safe_dump')
|
||||||
def test_update_passwords_env_update(self, mock_dump, mock_pw,
|
def test_update_passwords_env_update(self, mock_dump, mock_pw,
|
||||||
mock_exists):
|
mock_exists, mock_chmod):
|
||||||
pw_dict = {"GeneratedPassword": 123}
|
pw_dict = {"GeneratedPassword": 123}
|
||||||
pw_env_path = os.path.join(self.temp_homedir,
|
pw_conf_path = os.path.join(self.temp_homedir,
|
||||||
'tripleo-undercloud-passwords.yaml')
|
'undercloud-passwords.conf')
|
||||||
|
t_pw_conf_path = os.path.join(
|
||||||
|
self.temp_homedir, 'tripleo-undercloud-passwords.yaml')
|
||||||
|
|
||||||
mock_pw.return_value = pw_dict
|
mock_pw.return_value = pw_dict
|
||||||
mock_exists.return_value = True
|
mock_exists.return_value = True
|
||||||
|
with open(t_pw_conf_path, 'w') as t_pw:
|
||||||
|
t_pw.write('parameter_defaults: {ExistingKey: xyz}\n')
|
||||||
|
|
||||||
mock_open_context = mock.mock_open(
|
mock_open_context = mock.mock_open(
|
||||||
read_data='parameter_defaults: {ExistingKey: xyz}\n')
|
read_data='parameter_defaults: {ExistingKey: xyz}\n')
|
||||||
with mock.patch('six.moves.builtins.open', mock_open_context):
|
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||||
self.cmd._update_passwords_env(
|
self.cmd._update_passwords_env(self.temp_homedir,
|
||||||
passwords={'ADefault': 456, 'ExistingKey': 'dontupdate'})
|
passwords={'ADefault': 456,
|
||||||
|
'ExistingKey':
|
||||||
mock_exists.assert_called_once_with(pw_env_path)
|
'dontupdate'})
|
||||||
mock_open_context.assert_called_with(pw_env_path, 'w')
|
mock_open_context.assert_called_with(pw_conf_path, 'w')
|
||||||
mock_open_handle = mock_open_context()
|
|
||||||
expected_dict = {'parameter_defaults': {'GeneratedPassword': 123,
|
expected_dict = {'parameter_defaults': {'GeneratedPassword': 123,
|
||||||
'ExistingKey': 'xyz',
|
'ExistingKey': 'xyz',
|
||||||
'ADefault': 456}}
|
'ADefault': 456}}
|
||||||
mock_dump.assert_called_once_with(expected_dict,
|
mock_dump.assert_called_once_with(expected_dict,
|
||||||
mock_open_handle,
|
mock.ANY,
|
||||||
default_flow_style=False)
|
default_flow_style=False)
|
||||||
|
chmod_calls = [mock.call(t_pw_conf_path, 0o600),
|
||||||
|
mock.call(pw_conf_path, 0o600)]
|
||||||
|
mock_chmod.assert_has_calls(chmod_calls)
|
||||||
|
@@ -568,6 +568,8 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False):
|
|||||||
env_file = _write_env_file(env_data)
|
env_file = _write_env_file(env_data)
|
||||||
deploy_args += ['-e', env_file]
|
deploy_args += ['-e', env_file]
|
||||||
|
|
||||||
|
deploy_args += ['--output-dir=%s' % os.environ.get('HOME', '')]
|
||||||
|
|
||||||
if CONF.get('custom_env_files'):
|
if CONF.get('custom_env_files'):
|
||||||
for custom_file in CONF['custom_env_files']:
|
for custom_file in CONF['custom_env_files']:
|
||||||
deploy_args += ['-e', custom_file]
|
deploy_args += ['-e', custom_file]
|
||||||
|
@@ -20,6 +20,7 @@ import logging
|
|||||||
import netaddr
|
import netaddr
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -93,9 +94,10 @@ class DeployUndercloud(command.Command):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _update_passwords_env(self, passwords=None):
|
def _update_passwords_env(self, output_dir, passwords=None):
|
||||||
pw_file = os.path.join(os.environ.get('HOME', ''),
|
pw_file = os.path.join(output_dir, 'tripleo-undercloud-passwords.yaml')
|
||||||
'tripleo-undercloud-passwords.yaml')
|
undercloud_pw_file = os.path.join(output_dir,
|
||||||
|
'undercloud-passwords.conf')
|
||||||
stack_env = {'parameter_defaults': {}}
|
stack_env = {'parameter_defaults': {}}
|
||||||
if os.path.exists(pw_file):
|
if os.path.exists(pw_file):
|
||||||
with open(pw_file) as pf:
|
with open(pw_file) as pf:
|
||||||
@@ -111,8 +113,26 @@ class DeployUndercloud(command.Command):
|
|||||||
if p not in stack_env['parameter_defaults']:
|
if p not in stack_env['parameter_defaults']:
|
||||||
stack_env['parameter_defaults'][p] = v
|
stack_env['parameter_defaults'][p] = v
|
||||||
|
|
||||||
|
# Write out the password file in yaml for heat.
|
||||||
|
# This contains sensitive data so ensure it's not world-readable
|
||||||
with open(pw_file, 'w') as pf:
|
with open(pw_file, 'w') as pf:
|
||||||
yaml.safe_dump(stack_env, pf, default_flow_style=False)
|
yaml.safe_dump(stack_env, pf, default_flow_style=False)
|
||||||
|
# Using chmod here instead of permissions on the open above so we don't
|
||||||
|
# have to fight with umask.
|
||||||
|
os.chmod(pw_file, 0o600)
|
||||||
|
# Write out an instack undercloud compatible version.
|
||||||
|
# This contains sensitive data so ensure it's not world-readable
|
||||||
|
with open(undercloud_pw_file, 'w') as pf:
|
||||||
|
pf.write('[auth]\n')
|
||||||
|
for p, v in stack_env['parameter_defaults'].items():
|
||||||
|
if 'Password' in p or 'Token' in p:
|
||||||
|
# Convert camelcase from heat templates into the underscore
|
||||||
|
# format used by instack undercloud.
|
||||||
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', p)
|
||||||
|
pw_key = re.sub('([a-z0-9])([A-Z])',
|
||||||
|
r'\1_\2', s1).lower()
|
||||||
|
pf.write('undercloud_%s: %s\n' % (pw_key, v))
|
||||||
|
os.chmod(undercloud_pw_file, 0o600)
|
||||||
|
|
||||||
return pw_file
|
return pw_file
|
||||||
|
|
||||||
@@ -219,7 +239,7 @@ class DeployUndercloud(command.Command):
|
|||||||
environments.insert(0, resource_registry_path)
|
environments.insert(0, resource_registry_path)
|
||||||
|
|
||||||
# this will allow the user to overwrite passwords with custom envs
|
# this will allow the user to overwrite passwords with custom envs
|
||||||
pw_file = self._update_passwords_env()
|
pw_file = self._update_passwords_env(parsed_args.output_dir)
|
||||||
environments.insert(1, pw_file)
|
environments.insert(1, pw_file)
|
||||||
|
|
||||||
undercloud_env_path = os.path.join(
|
undercloud_env_path = os.path.join(
|
||||||
@@ -329,9 +349,8 @@ class DeployUndercloud(command.Command):
|
|||||||
msg = 'Stack creation timeout: %d minutes elapsed' % (timeout)
|
msg = 'Stack creation timeout: %d minutes elapsed' % (timeout)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
||||||
def _download_ansible_playbooks(self, client, stack_name):
|
def _download_ansible_playbooks(self, client, stack_name, output_dir):
|
||||||
stack_config = config.Config(client)
|
stack_config = config.Config(client)
|
||||||
output_dir = os.environ.get('HOME')
|
|
||||||
|
|
||||||
print('** Downloading undercloud ansible.. **')
|
print('** Downloading undercloud ansible.. **')
|
||||||
# python output buffering is making this seem to take forever..
|
# python output buffering is making this seem to take forever..
|
||||||
@@ -381,6 +400,11 @@ class DeployUndercloud(command.Command):
|
|||||||
parser.add_argument('--stack',
|
parser.add_argument('--stack',
|
||||||
help=_("Stack name to create"),
|
help=_("Stack name to create"),
|
||||||
default='undercloud')
|
default='undercloud')
|
||||||
|
parser.add_argument('--output-dir',
|
||||||
|
dest='output_dir',
|
||||||
|
help=_("Directory to output state and ansible"
|
||||||
|
" deployment files."),
|
||||||
|
default=os.environ.get('HOME', ''))
|
||||||
parser.add_argument('-t', '--timeout', metavar='<TIMEOUT>',
|
parser.add_argument('-t', '--timeout', metavar='<TIMEOUT>',
|
||||||
type=int, default=30,
|
type=int, default=30,
|
||||||
help=_('Deployment timeout in minutes.'))
|
help=_('Deployment timeout in minutes.'))
|
||||||
@@ -481,7 +505,8 @@ class DeployUndercloud(command.Command):
|
|||||||
# download the ansible playbooks and execute them.
|
# download the ansible playbooks and execute them.
|
||||||
ansible_dir = \
|
ansible_dir = \
|
||||||
self._download_ansible_playbooks(orchestration_client,
|
self._download_ansible_playbooks(orchestration_client,
|
||||||
parsed_args.stack)
|
parsed_args.stack,
|
||||||
|
parsed_args.output_dir)
|
||||||
# Kill heat, we're done with it now.
|
# Kill heat, we're done with it now.
|
||||||
self._kill_heat()
|
self._kill_heat()
|
||||||
# Never returns.. We exec() it directly.
|
# Never returns.. We exec() it directly.
|
||||||
|
Reference in New Issue
Block a user