Implement utils.copy_clouds_yaml

copy_clouds_yaml is a new function that will be used to copy
/etc/openstack/clouds.yaml which is now generated by Ansible in TripleO
Heat Templates: https://review.opendev.org/706850

It also sets the right permissions to only allow the deployment user to
read the file which container sensible credentials (cloud admin).

We also have to backport: I286c71edf120e11d8e51c792e6078dc60147f026

Depends-On: https://review.opendev.org/#/c/706850/
Change-Id: I49b95bf7a03e39db3c2cbb91b2167c7645b09136
(cherry picked from commit ccd4c7f59f)
This commit is contained in:
Emilien Macchi 2019-12-17 10:41:22 -05:00
parent e7706b6f1e
commit 629a35f00b
5 changed files with 107 additions and 36 deletions

View File

@ -93,6 +93,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.cmd._download_missing_files_from_plan = self.real_download_missing
shutil.rmtree = self.real_shutil
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -111,7 +112,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_create_parameters_env,
mock_breakpoints_cleanup,
mock_events, mock_stack_network_check,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
clients = self.app.client_manager
@ -188,7 +189,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
template_object=constants.OVERCLOUD_YAML_NAME)
mock_create_tempest_deployer_input.assert_called_with()
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -209,7 +212,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_breakpoints_cleanup,
mock_postconfig,
mock_invoke_plan_env_wf,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
@ -308,7 +311,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
env_map = yaml.safe_load(f)
self.assertEqual(env_map.get('parameter_defaults'),
parameters_env.get('parameter_defaults'))
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -339,7 +344,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_postconfig, mock_shutil_rmtree,
mock_invoke_plan_env_wf,
mock_stack_network_check,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
plane_management_fixture = deployment.PlanManagementFixture()
@ -445,7 +450,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
[mock.call('the-plan-environment.yaml')])
clients.tripleoclient.object_store.put_object.assert_called()
self.assertTrue(mock_invoke_plan_env_wf.called)
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -470,7 +477,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_create_parameters_env, mock_validate_args,
mock_breakpoints_cleanup,
mock_postconfig, mock_deprecated_params, mock_stack_network_check,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
plane_management_fixture = deployment.PlanManagementFixture()
@ -533,7 +540,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
deploy_plan_call = execution_calls[1]
deploy_plan_call_input = deploy_plan_call[1]['workflow_input']
self.assertTrue(deploy_plan_call_input['skip_deploy_identifier'])
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -552,7 +561,8 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_deploy_postconfig,
mock_breakpoints_cleanup,
mock_events, mock_stack_network_check,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry,
mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
plane_management_fixture = deployment.PlanManagementFixture()
@ -602,6 +612,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
template_object=constants.OVERCLOUD_YAML_NAME)
mock_create_tempest_deployer_input.assert_called_with()
mock_copy.assert_called_once()
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_deploy_tripleo_heat_templates', autospec=True)
@ -620,6 +631,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.cmd.take_action, parsed_args)
self.assertFalse(mock_deploy_tht.called)
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.utils.check_stack_network_matches_env_files')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_deploy_postconfig', autospec=True)
@ -629,7 +641,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
'_heat_deploy', autospec=True)
def test_environment_dirs(self, mock_deploy_heat,
mock_update_parameters, mock_post_config,
mock_stack_network_check):
mock_stack_network_check, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
plane_management_fixture = deployment.PlanManagementFixture()
@ -683,6 +695,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.check_stack_network_matches_env_files')
@mock.patch('tripleoclient.utils.get_stack', autospec=True)
@ -929,6 +942,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertFalse(utils_fixture.mock_create_ocrc.called)
self.assertFalse(mock_create_tempest_deployer_input.called)
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -939,7 +953,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
@mock.patch('shutil.rmtree', autospec=True)
def test_answers_file(self, mock_rmtree, mock_tmpdir,
mock_heat_deploy, mock_stack_network_check,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
clients = self.app.client_manager
@ -1014,6 +1028,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertIn('Test2', call_args[8]['resource_registry'])
utils_fixture.mock_deploy_tht.assert_called_with()
mock_copy.assert_called_once()
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
@ -1072,6 +1087,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.cmd.take_action,
parsed_args)
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -1097,7 +1113,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_breakpoints_cleanup,
mock_deploy_post_config,
mock_stack_network_check,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
plane_management_fixture = deployment.PlanManagementFixture()
@ -1182,6 +1198,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
mock_create_tempest_deployer_input.assert_called_with()
mock_validate_args.assert_called_once_with(parsed_args)
mock_copy.assert_called_once()
@mock.patch('tripleoclient.workflows.parameters.'
'check_deprecated_parameters', autospec=True)
@ -1250,6 +1267,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertRaises(exceptions.StackInProgress,
self.cmd.take_action, parsed_args)
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -1258,7 +1276,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_deploy_tripleo_heat_templates_tmpdir', autospec=True)
def test_deployed_server(self, mock_deploy_tmpdir, mock_overcloudrc,
mock_get_undercloud_host_entry):
mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
utils_fixture = deployment.UtilsOvercloudFixture()
@ -1281,6 +1299,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertNotCalled(clients.baremetal)
self.assertNotCalled(clients.compute)
self.assertTrue(utils_fixture.mock_deploy_tht.called)
mock_copy.assert_called_once()
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_deploy_tripleo_heat_templates', autospec=True)
@ -1298,6 +1317,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
parsed_args)
self.assertFalse(mock_deploy_tmpdir.called)
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -1307,7 +1327,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
'_deploy_tripleo_heat_templates_tmpdir', autospec=True)
def test_config_download(
self, mock_deploy_tmpdir,
mock_overcloudrc, mock_get_undercloud_host_entry):
mock_overcloudrc, mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
utils_fixture = deployment.UtilsOvercloudFixture()
@ -1331,7 +1351,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertEqual(
'deploying',
fixture.mock_set_deployment_status.call_args[0][1])
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -1341,7 +1363,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
'_deploy_tripleo_heat_templates_tmpdir', autospec=True)
def test_config_download_only(
self, mock_deploy_tmpdir,
mock_overcloudrc, mock_get_undercloud_host_entry):
mock_overcloudrc, mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
utils_fixture = deployment.UtilsOvercloudFixture()
@ -1365,6 +1387,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertEqual(
'deploying',
fixture.mock_set_deployment_status.call_args[0][1])
mock_copy.assert_called_once()
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
@ -1409,6 +1432,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
'failed',
fixture.mock_set_deployment_status.call_args[0][1])
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -1418,7 +1442,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
'_deploy_tripleo_heat_templates_tmpdir', autospec=True)
def test_override_ansible_cfg(
self, mock_deploy_tmpdir,
mock_overcloudrc, mock_get_undercloud_host_entry):
mock_overcloudrc, mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
utils_fixture = deployment.UtilsOvercloudFixture()
@ -1440,7 +1464,9 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.assertTrue(fixture.mock_config_download.called)
self.assertEqual('ansible.cfg',
fixture.mock_config_download.call_args[0][8])
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.'
'_get_undercloud_host_entry', autospec=True,
return_value='192.168.0.1 uc.ctlplane.localhost uc.ctlplane')
@ -1450,7 +1476,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
'_deploy_tripleo_heat_templates_tmpdir', autospec=True)
def test_config_download_timeout(
self, mock_deploy_tmpdir,
mock_overcloudrc, mock_get_undercloud_host_entry):
mock_overcloudrc, mock_get_undercloud_host_entry, mock_copy):
fixture = deployment.DeploymentWorkflowFixture()
self.useFixture(fixture)
utils_fixture = deployment.UtilsOvercloudFixture()
@ -1469,6 +1495,7 @@ class TestDeployOvercloud(fakes.TestDeployOvercloud):
self.cmd.take_action(parsed_args)
fixture.mock_config_download.assert_called()
self.assertEqual(240*60, fixture.mock_config_download.call_args[0][9])
mock_copy.assert_called_once()
def test_download_missing_files_from_plan(self):
# Restore the real function so we don't accidentally call the mock

View File

@ -887,6 +887,7 @@ class TestDeployUndercloud(TestPluginV1):
)
@mock.patch('tripleoclient.utils.reset_cmdline')
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.'
'_download_stack_outputs')
@mock.patch('tripleo_common.actions.ansible.'
@ -938,7 +939,7 @@ class TestDeployUndercloud(TestPluginV1):
mock_tarball, mock_templates_dir,
mock_open, mock_os, mock_user, mock_cc,
mock_chmod, mock_ac, mock_outputs,
mock_cmdline):
mock_copy, mock_cmdline):
mock_slink.side_effect = 'fake-cmd'
parsed_args = self.check_parser(self.cmd,
['--local-ip', '127.0.0.1',
@ -975,7 +976,8 @@ class TestDeployUndercloud(TestPluginV1):
@mock.patch('tripleoclient.utils.reset_cmdline')
@mock.patch('tripleoclient.utils.ansible_symlink')
def test_take_action(self, mock_slink, mock_cmdline):
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
def test_take_action(self, mock_copy, mock_slink, mock_cmdline):
mock_slink.side_effect = 'fake-cmd'
parsed_args = self.check_parser(self.cmd,
['--local-ip', '127.0.0.1',
@ -985,12 +987,15 @@ class TestDeployUndercloud(TestPluginV1):
self.assertRaises(exceptions.DeploymentError,
self.cmd.take_action, parsed_args)
mock_cmdline.assert_called_once()
mock_copy.assert_called_once()
@mock.patch('tripleoclient.utils.reset_cmdline')
@mock.patch('tripleoclient.utils.copy_clouds_yaml')
@mock.patch('tripleoclient.v1.tripleo_deploy.Deploy._standalone_deploy',
return_value=1)
@mock.patch('tripleoclient.utils.ansible_symlink')
def test_take_action_failure(self, mock_slink, mock_deploy, mock_cmdline):
def test_take_action_failure(self, mock_slink, mock_deploy, mock_copy,
mock_cmdline):
mock_slink.side_effect = 'fake-cmd'
parsed_args = self.check_parser(self.cmd,
['--local-ip', '127.0.0.1',
@ -1001,6 +1006,7 @@ class TestDeployUndercloud(TestPluginV1):
self.assertRaises(exceptions.DeploymentError,
self.cmd.take_action, parsed_args)
mock_cmdline.assert_called_once()
mock_copy.assert_called_once()
@mock.patch('os.path.isfile', return_value=False)
def test_set_stack_action_default_create(self, mock_isfile):

View File

@ -2008,3 +2008,53 @@ def safe_write(path, data):
'created. Error: %(msg)') % {'file': path,
'msg': error.message}
raise oscexc.CommandError(msg)
def copy_clouds_yaml(user):
"""Copy clouds.yaml file from /etc/openstack to deployment user's home
:param user: deployment user
"""
clouds_etc_file = '/etc/openstack/clouds.yaml'
clouds_home_dir = os.path.join('/home', user)
clouds_config_dir = os.path.join(clouds_home_dir, '.config/openstack')
clouds_config_file = os.path.join(clouds_config_dir, 'clouds.yaml')
clouds_user_id = os.stat(clouds_home_dir).st_uid
clouds_group_id = os.stat(clouds_home_dir).st_gid
# If the file doesn't exist (e.g. on a minion node), we don't need to copy
# /etc/openstack/clouds.yaml to the user directory.
if not os.path.isfile(clouds_etc_file):
return
if not os.path.exists(clouds_config_dir):
try:
os.makedirs(clouds_config_dir)
except OSError as e:
messages = _("Unable to create credentials directory: "
"{0}, {1}").format(clouds_config_dir, e)
raise OSError(messages)
# Using 'sudo' here as for the overcloud the deployment command is run
# from regular deployment user.
cp_args = ['sudo', 'cp', clouds_etc_file, clouds_config_dir]
if run_command_and_log(LOG, cp_args) != 0:
msg = _('Error when user %(user)s tried to copy %(src)s to %(dest)s'
' with sudo') % {'user': user, 'src': clouds_etc_file,
'dest': clouds_config_dir}
LOG.error(msg)
raise exceptions.DeploymentError(msg)
chmod_args = ['sudo', 'chmod', '0600', clouds_config_file]
if run_command_and_log(LOG, chmod_args) != 0:
msg = _('Error when user %(user)s tried to chmod %(file)s file'
' with sudo') % {'user': user, 'file': clouds_config_file}
LOG.error(msg)
raise exceptions.DeploymentError(msg)
chown_args = ['sudo', 'chown', '-R',
str(clouds_user_id) + ':' + str(clouds_group_id),
clouds_config_dir]
if run_command_and_log(LOG, chown_args) != 0:
msg = _('Error when user %(user)s tried to chown %(dir)s directory'
' with sudo') % {'user': user, 'dir': clouds_config_dir}
LOG.error(msg)
raise exceptions.DeploymentError(msg)

View File

@ -20,6 +20,7 @@ import logging
import os
import os.path
from prettytable import PrettyTable
from pwd import getpwuid
import re
import shutil
import six
@ -34,14 +35,6 @@ from osc_lib.i18n import _
from swiftclient.exceptions import ClientException
from tripleo_common import update
# FIXME(chkumar246): Once https://review.opendev.org/664568 gets merged
# and new version of tripleo-common gots released, It requires a version
# bump in requirements.txt.
try:
from tripleo_common.utils import clouds_yaml
except ImportError:
from tripleoclient.v1 import mock_clouds_yaml as clouds_yaml
from tripleoclient import command
from tripleoclient import constants
from tripleoclient import exceptions
@ -1012,18 +1005,9 @@ class DeployOvercloud(command.Command):
self.clients, container=stack.stack_name,
no_proxy=parsed_args.no_proxy)
# Create overcloud clouds.yaml
cloud_data = deployment.create_cloudsyaml(
self.clients, container=stack.stack_name)
cloud_yaml_dir = os.path.join(constants.CLOUD_HOME_DIR,
constants.CLOUDS_YAML_DIR)
cloud_user_id = os.stat(constants.CLOUD_HOME_DIR).st_uid
cloud_group_id = os.stat(constants.CLOUD_HOME_DIR).st_gid
clouds_yaml.create_clouds_yaml(
cloud=cloud_data,
cloud_yaml_dir=cloud_yaml_dir,
user_id=cloud_user_id,
group_id=cloud_group_id)
# Copy clouds.yaml to the cloud user directory
user = getpwuid(os.stat(constants.CLOUD_HOME_DIR).st_uid).pw_name
utils.copy_clouds_yaml(user)
rcpath = utils.write_overcloudrc(stack.stack_name, overcloudrcs)
utils.create_tempest_deployer_input()

View File

@ -1450,6 +1450,10 @@ class Deploy(command.Command):
self.log.error(msg)
raise exceptions.DeploymentError(msg)
finally:
# Copy clouds.yaml from /etc/openstack so credentials can be
# read by the deployment user and not only root.
utils.copy_clouds_yaml(parsed_args.deployment_user)
# send erase sequence to reset the cmdline if paunch/ansible
# mangled some escape sequences
utils.reset_cmdline()