summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/cfn-signal15
-rw-r--r--heat_cfntools/cfntools/cfn_helper.py235
-rw-r--r--heat_cfntools/tests/test_cfn_helper.py324
3 files changed, 329 insertions, 245 deletions
diff --git a/bin/cfn-signal b/bin/cfn-signal
index a8ab7c7..7fce58e 100755
--- a/bin/cfn-signal
+++ b/bin/cfn-signal
@@ -100,14 +100,19 @@ body = {
"UniqueId": unique_id,
"Data": args.data
}
+data = cfn_helper.json.dumps(body)
-insecure = ""
+cmd = ['curl']
if args.insecure:
- insecure = "--insecure"
+ cmd.append('--insecure')
+cmd.extend([
+ '-X', 'PUT',
+ '-H', 'Content-Type:',
+ '--data-binary', data,
+ args.url
+])
-cmd_str = ("curl %s -X PUT -H \'Content-Type:\' --data-binary \'%s\' \"%s\"" %
- (insecure, cfn_helper.json.dumps(body), args.url))
-command = cfn_helper.CommandRunner(cmd_str).run()
+command = cfn_helper.CommandRunner(cmd).run()
if command.status != 0:
LOG.error(command.stderr)
sys.exit(command.status)
diff --git a/heat_cfntools/cfntools/cfn_helper.py b/heat_cfntools/cfntools/cfn_helper.py
index 2351c1d..e241e57 100644
--- a/heat_cfntools/cfntools/cfn_helper.py
+++ b/heat_cfntools/cfntools/cfn_helper.py
@@ -1,4 +1,4 @@
-#
+
# 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
@@ -19,6 +19,7 @@ Not implemented yet:
- placeholders are ignored
"""
import atexit
+import contextlib
import errno
import functools
import grp
@@ -40,6 +41,7 @@ import six.moves.configparser as ConfigParser
import subprocess
import tempfile
+
# Override BOTO_CONFIG, which makes boto look only at the specified
# config file, instead of the default locations
os.environ['BOTO_CONFIG'] = '/var/lib/heat-cfntools/cfn-boto-cfg'
@@ -138,7 +140,7 @@ class Hook(object):
def event(self, ev_name, ev_object, ev_resource):
if self.resource_name_get() == ev_resource and \
ev_name in self.triggers:
- CommandRunner(self.action).run(user=self.runas)
+ CommandRunner(self.action, shell=True).run(user=self.runas)
else:
LOG.debug('event: {%s, %s, %s} did not match %s' %
(ev_name, ev_object, ev_resource, self.__str__()))
@@ -152,11 +154,39 @@ class Hook(object):
self.action)
+class ControlledPrivilegesFailureException(Exception):
+ pass
+
+
+@contextlib.contextmanager
+def controlled_privileges(user):
+ orig_euid = None
+ try:
+ real = pwd.getpwnam(user)
+ if os.geteuid() != real.pw_uid:
+ orig_euid = os.geteuid()
+ os.seteuid(real.pw_uid)
+ LOG.debug("Privileges set for user %s" % user)
+ except Exception as e:
+ raise ControlledPrivilegesFailureException(e)
+
+ try:
+ yield
+ finally:
+ if orig_euid is not None:
+ try:
+ os.seteuid(orig_euid)
+ LOG.debug("Original privileges restored.")
+ except Exception as e:
+ LOG.error("Error restoring privileges %s" % e)
+
+
class CommandRunner(object):
"""Helper class to run a command and store the output."""
- def __init__(self, command, nextcommand=None):
+ def __init__(self, command, shell=False, nextcommand=None):
self._command = command
+ self._shell = shell
self._next = nextcommand
self._stdout = None
self._stderr = None
@@ -182,33 +212,32 @@ class CommandRunner(object):
LOG.debug("Running command: %s" % self._command)
cmd = self._command
- if isinstance(cmd, str):
- cmd = cmd.split()
+ shell = self._shell
- if user != 'root':
- # lower the privileges
- try:
- real = pwd.getpwnam(user)
- os.setuid(real.pw_uid)
- except Exception as e:
- LOG.error("Error setting privileges for user '%s': %s"
- % (user, e))
- # 126: command invoked cannot be executed
- self._status = 126
- return
+ # Ensure commands that are given as string are run on shell
+ assert isinstance(cmd, six.string_types) is bool(shell)
- subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, cwd=cwd, env=env)
- output = subproc.communicate()
+ try:
+ with controlled_privileges(user):
+ subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, cwd=cwd,
+ env=env, shell=shell)
+ output = subproc.communicate()
+ self._status = subproc.returncode
+ self._stdout = output[0]
+ self._stderr = output[1]
+ except ControlledPrivilegesFailureException as e:
+ LOG.error("Error setting privileges for user '%s': %s"
+ % (user, e))
+ self._status = 126
+ self._stderr = six.text_type(e)
- self._status = subproc.returncode
- self._stdout = output[0]
- self._stderr = output[1]
if self._status:
LOG.debug("Return code of %d after executing: '%s'\n"
"stdout: '%s'\n"
"stderr: '%s'" % (self._status, cmd, self._stdout,
self._stderr))
+
if self._next:
self._next.run()
return self
@@ -291,7 +320,8 @@ class RpmHelper(object):
e.g., httpd-2.2.22
e.g., httpd-2.2.22-1.fc16
"""
- command = CommandRunner("rpm -q %s" % pkg).run()
+ cmd = ['rpm', '-q', pkg]
+ command = CommandRunner(cmd).run()
return command.status == 0
@classmethod
@@ -304,8 +334,8 @@ class RpmHelper(object):
e.g., httpd-2.2.22
e.g., httpd-2.2.22-1.fc16
"""
- cmd_str = "yum -y --showduplicates list available %s" % pkg
- command = CommandRunner(cmd_str).run()
+ cmd = ['yum', '-y', '--showduplicates', 'list', 'available', pkg]
+ command = CommandRunner(cmd).run()
return command.status == 0
@classmethod
@@ -318,8 +348,8 @@ class RpmHelper(object):
e.g., httpd-2.2.22
e.g., httpd-2.2.22-1.fc21
"""
- cmd_str = "dnf -y --showduplicates list available %s" % pkg
- command = CommandRunner(cmd_str).run()
+ cmd = ['dnf', '-y', '--showduplicates', 'list', 'available', pkg]
+ command = CommandRunner(cmd).run()
return command.status == 0
@classmethod
@@ -332,8 +362,8 @@ class RpmHelper(object):
e.g., httpd-2.2.22
e.g., httpd-2.2.22-1.fc16
"""
- cmd_str = "zypper -n --no-refresh search %s" % pkg
- command = CommandRunner(cmd_str).run()
+ cmd = ['zypper', '-n', '--no-refresh', 'search', pkg]
+ command = CommandRunner(cmd).run()
return command.status == 0
@classmethod
@@ -359,22 +389,16 @@ class RpmHelper(object):
* packages must be in same format as yum pkg list
"""
if rpms:
- cmd = "rpm -U --force --nosignature "
- cmd += " ".join(packages)
- LOG.info("Installing packages: %s" % cmd)
+ cmd = ['rpm', '-U', '--force', '--nosignature']
elif zypper:
- cmd = "zypper -n install "
- cmd += " ".join(packages)
- LOG.info("Installing packages: %s" % cmd)
+ cmd = ['zypper', '-n', 'install']
elif dnf:
# use dnf --best to upgrade outdated-but-installed packages
- cmd = "dnf -y --best install "
- cmd += " ".join(packages)
- LOG.info("Installing packages: %s" % cmd)
+ cmd = ['dnf', '-y', '--best', 'install']
else:
- cmd = "yum -y install "
- cmd += " ".join(packages)
- LOG.info("Installing packages: %s" % cmd)
+ cmd = ['yum', '-y', 'install']
+ cmd.extend(packages)
+ LOG.info("Installing packages: %s" % cmd)
command = CommandRunner(cmd).run()
if command.status:
LOG.warn("Failed to install packages: %s" % cmd)
@@ -400,22 +424,22 @@ class RpmHelper(object):
if rpms:
cls.install(packages)
elif zypper:
- cmd = "zypper -n install --oldpackage "
- cmd += " ".join(packages)
+ cmd = ['zypper', '-n', 'install', '--oldpackage']
+ cmd.extend(packages)
LOG.info("Downgrading packages: %s", cmd)
command = CommandRunner(cmd).run()
if command.status:
LOG.warn("Failed to downgrade packages: %s" % cmd)
elif dnf:
- cmd = "dnf -y downgrade "
- cmd += " ".join(packages)
+ cmd = ['dnf', '-y', 'downgrade']
+ cmd.extend(packages)
LOG.info("Downgrading packages: %s", cmd)
command = CommandRunner(cmd).run()
if command.status:
LOG.warn("Failed to downgrade packages: %s" % cmd)
else:
- cmd = "yum -y downgrade "
- cmd += " ".join(packages)
+ cmd = ['yum', '-y', 'downgrade']
+ cmd.extend(packages)
LOG.info("Downgrading packages: %s" % cmd)
command = CommandRunner(cmd).run()
if command.status:
@@ -449,22 +473,23 @@ class PackagesHandler(object):
# TODO(asalkeld) support versions
# -b == local & remote install
# -y == install deps
- opts = '-b -y'
+ opts = ['-b', '-y']
for pkg_name, versions in packages.items():
if len(versions) > 0:
- cmd_str = 'gem install %s --version %s %s' % (opts,
- versions[0],
- pkg_name)
- CommandRunner(cmd_str).run()
+ cmd = ['gem', 'install'] + opts
+ cmd.extend(['--version', versions[0], pkg_name])
+ CommandRunner(cmd).run()
else:
- CommandRunner('gem install %s %s' % (opts, pkg_name)).run()
+ cmd = ['gem', 'install'] + opts
+ cmd.append(pkg_name)
+ CommandRunner(cmd).run()
def _handle_python_packages(self, packages):
"""very basic support for easy_install."""
# TODO(asalkeld) support versions
for pkg_name, versions in packages.items():
- cmd_str = 'easy_install %s' % (pkg_name)
- CommandRunner(cmd_str).run()
+ cmd = ['easy_install', pkg_name]
+ CommandRunner(cmd).run()
def _handle_zypper_packages(self, packages):
"""Handle installation, upgrade, or downgrade of packages via yum.
@@ -576,7 +601,8 @@ class PackagesHandler(object):
* if a version array is supplied, choose the highest version from the
array and follow same logic for version string above
"""
- cmd = CommandRunner("which yum").run()
+
+ cmd = CommandRunner(['which', 'yum']).run()
if cmd.status == 1:
# yum not available, use DNF if available
self._handle_dnf_packages(packages)
@@ -629,11 +655,11 @@ class PackagesHandler(object):
def _handle_apt_packages(self, packages):
"""very basic support for apt."""
# TODO(asalkeld) support versions
- pkg_list = ' '.join([p for p in packages])
+ pkg_list = list(packages)
- cmd_str = 'DEBIAN_FRONTEND=noninteractive apt-get -y install %s' % \
- pkg_list
- CommandRunner(cmd_str).run()
+ env = {'DEBIAN_FRONTEND': 'noninteractive'}
+ cmd = ['apt-get', '-y', 'install'] + pkg_list
+ CommandRunner(cmd).run(env=env)
# map of function pointers to handle different package managers
_package_handlers = {"yum": _handle_yum_packages,
@@ -709,7 +735,7 @@ class FilesHandler(object):
.encode('UTF-8'))
f.close()
elif 'source' in meta:
- CommandRunner('curl -o %s %s' % (dest, meta['source'])).run()
+ CommandRunner(['curl', '-o', dest, meta['source']]).run()
else:
LOG.error('%s %s' % (dest, str(meta)))
continue
@@ -810,8 +836,9 @@ class SourcesHandler(object):
def _apply_source(self, dest, url):
cmd = self._apply_source_cmd(dest, url)
+ #FIXME bug 1498298
if cmd != '':
- runner = CommandRunner(cmd)
+ runner = CommandRunner(cmd, shell=True)
runner.run()
def apply_sources(self):
@@ -833,47 +860,52 @@ class ServicesHandler(object):
if os.path.exists("/bin/systemctl"):
service_exe = "/bin/systemctl"
service = '%s.service' % service
- service_start = '%s start %s'
- service_status = '%s status %s'
- service_stop = '%s stop %s'
+ service_start = [service_exe, 'start', service]
+ service_status = [service_exe, 'status', service]
+ service_stop = [service_exe, 'stop', service]
elif os.path.exists("/sbin/service"):
service_exe = "/sbin/service"
- service_start = '%s %s start'
- service_status = '%s %s status'
- service_stop = '%s %s stop'
+ service_start = [service_exe, service, 'start']
+ service_status = [service_exe, service, 'status']
+ service_stop = [service_exe, service, 'stop']
else:
service_exe = "/usr/sbin/service"
- service_start = '%s %s start'
- service_status = '%s %s status'
- service_stop = '%s %s stop'
+ service_start = [service_exe, service, 'start']
+ service_status = [service_exe, service, 'status']
+ service_stop = [service_exe, service, 'stop']
if os.path.exists("/bin/systemctl"):
enable_exe = "/bin/systemctl"
- enable_on = '%s enable %s'
- enable_off = '%s disable %s'
+ enable_on = [enable_exe, 'enable', service]
+ enable_off = [enable_exe, 'disable', service]
elif os.path.exists("/sbin/chkconfig"):
enable_exe = "/sbin/chkconfig"
- enable_on = '%s %s on'
- enable_off = '%s %s off'
+ enable_on = [enable_exe, service, 'on']
+ enable_off = [enable_exe, service, 'off']
+
else:
enable_exe = "/usr/sbin/update-rc.d"
- enable_on = '%s %s enable'
- enable_off = '%s %s disable'
+ enable_on = [enable_exe, service, 'enable']
+ enable_off = [enable_exe, service, 'disable']
- cmd = ""
+ cmd = None
if "enable" == command:
- cmd = enable_on % (enable_exe, service)
+ cmd = enable_on
elif "disable" == command:
- cmd = enable_off % (enable_exe, service)
+ cmd = enable_off
elif "start" == command:
- cmd = service_start % (service_exe, service)
+ cmd = service_start
elif "stop" == command:
- cmd = service_stop % (service_exe, service)
+ cmd = service_stop
elif "status" == command:
- cmd = service_status % (service_exe, service)
- command = CommandRunner(cmd)
- command.run()
- return command
+ cmd = service_status
+
+ if cmd is not None:
+ command = CommandRunner(cmd)
+ command.run()
+ return command
+ else:
+ LOG.error("Unknown sysv command %s" % command)
def _initialize_service(self, handler, service, properties):
if "enabled" in properties:
@@ -1056,7 +1088,7 @@ class CommandsHandler(object):
return
if "test" in properties:
- test = CommandRunner(properties["test"])
+ test = CommandRunner(properties["test"], shell=True)
test_status = test.run('root', cwd, env).status
if test_status != 0:
LOG.info("%s test returns false, skipping command"
@@ -1068,10 +1100,11 @@ class CommandsHandler(object):
if "command" in properties:
try:
command = properties["command"]
+ #FIXME bug 1498300
if isinstance(command, list):
escape = lambda x: '"%s"' % x.replace('"', '\\"')
command = ' '.join(map(escape, command))
- command = CommandRunner(command)
+ command = CommandRunner(command, shell=True)
command.run('root', cwd, env)
command_status = command.status
except OSError as e:
@@ -1110,14 +1143,11 @@ class GroupsHandler(object):
def _initialize_group(self, group, properties):
gid = properties.get("gid", None)
-
- param_list = []
- param_list.append(group)
-
+ cmd = ['groupadd', group]
if gid is not None:
- param_list.append("--gid " + gid)
+ cmd.extend(['--gid', str(gid)])
- command = CommandRunner("groupadd " + ' '.join(param_list))
+ command = CommandRunner(cmd)
command.run()
command_status = command.status
@@ -1156,23 +1186,23 @@ class UsersHandler(object):
uid = properties.get("uid", None)
homeDir = properties.get("homeDir", None)
- param_list = []
- param_list.append(user)
+ cmd = ['useradd', user]
if uid is not None:
- param_list.append("--uid " + uid)
+ cmd.extend(['--uid', six.text_type(uid)])
if homeDir is not None:
- param_list.append("--home " + homeDir)
+ cmd.extend(['--home', six.text_type(homeDir)])
if "groups" in properties:
- param_list.append("--groups " + ','.join(properties["groups"]))
+ groups = ','.join(properties["groups"])
+ cmd.extend(['--groups', groups])
#Users are created as non-interactive system users with a shell
#of /sbin/nologin. This is by design and cannot be modified.
- param_list.append("--shell /sbin/nologin")
+ cmd.extend(['--shell', '/sbin/nologin'])
- command = CommandRunner("useradd " + ' '.join(param_list))
+ command = CommandRunner(cmd)
command.run()
command_status = command.status
@@ -1265,7 +1295,8 @@ class Metadata(object):
url = 'http://169.254.169.254/openstack/2012-08-10/meta_data.json'
if not os.path.exists(cache_path):
- CommandRunner('curl -o %s %s' % (cache_path, url)).run()
+ cmd = ['curl', '-o', cache_path, url]
+ CommandRunner(cmd).run()
try:
with open(cache_path) as fd:
try:
diff --git a/heat_cfntools/tests/test_cfn_helper.py b/heat_cfntools/tests/test_cfn_helper.py
index 15641bc..308ca03 100644
--- a/heat_cfntools/tests/test_cfn_helper.py
+++ b/heat_cfntools/tests/test_cfn_helper.py
@@ -26,10 +26,11 @@ import testtools.matchers as ttm
from heat_cfntools.cfntools import cfn_helper
-def popen_root_calls(calls):
- kwargs = {'env': None, 'cwd': None, 'stderr': -1, 'stdout': -1}
+def popen_root_calls(calls, shell=False):
+ kwargs = {'env': None, 'cwd': None, 'stderr': -1, 'stdout': -1,
+ 'shell': shell}
return [
- mock.call(call.split(), **kwargs)
+ mock.call(call, **kwargs)
for call in calls
]
@@ -47,9 +48,12 @@ class FakePOpen():
pass
+@mock.patch.object(cfn_helper.pwd, 'getpwnam')
+@mock.patch.object(cfn_helper.os, 'seteuid')
+@mock.patch.object(cfn_helper.os, 'geteuid')
class TestCommandRunner(testtools.TestCase):
- def test_command_runner(self):
+ def test_command_runner(self, mock_geteuid, mock_seteuid, mock_getpwnam):
def returns(*args, **kwargs):
if args[0][0] == '/bin/command1':
return FakePOpen('All good')
@@ -60,74 +64,88 @@ class TestCommandRunner(testtools.TestCase):
with mock.patch('subprocess.Popen') as mock_popen:
mock_popen.side_effect = returns
- cmd2 = cfn_helper.CommandRunner('/bin/command2')
- cmd1 = cfn_helper.CommandRunner('/bin/command1', cmd2)
+ cmd2 = cfn_helper.CommandRunner(['/bin/command2'])
+ cmd1 = cfn_helper.CommandRunner(['/bin/command1'],
+ nextcommand=cmd2)
cmd1.run('root')
self.assertEqual(
- 'CommandRunner:\n\tcommand: /bin/command1\n\tstdout: All good',
+ 'CommandRunner:\n\tcommand: [\'/bin/command1\']\n\tstdout: '
+ 'All good',
str(cmd1))
self.assertEqual(
- 'CommandRunner:\n\tcommand: /bin/command2\n\tstatus: -1\n'
- '\tstdout: Doing something\n\tstderr: error',
+ 'CommandRunner:\n\tcommand: [\'/bin/command2\']\n\tstatus: '
+ '-1\n\tstdout: Doing something\n\tstderr: error',
str(cmd2))
- calls = popen_root_calls(['/bin/command1', '/bin/command2'])
+ calls = popen_root_calls([['/bin/command1'], ['/bin/command2']])
mock_popen.assert_has_calls(calls)
- def test_runner_runs_command_as_list(self):
- with mock.patch('subprocess.Popen') as mock_popen:
- command = '/bin/command --option=value arg1 arg2'
- expected_cmd = ['/bin/command', '--option=value', 'arg1', 'arg2']
- cmd = cfn_helper.CommandRunner(command)
- cmd.run()
- self.assertTrue(mock_popen.called)
- actual_cmd = mock_popen.call_args[0][0]
- self.assertTrue(isinstance(actual_cmd, list))
- self.assertItemsEqual(expected_cmd, actual_cmd)
-
- @mock.patch.object(cfn_helper.pwd, 'getpwnam')
- @mock.patch.object(cfn_helper.os, 'setuid')
- def test_privileges_are_lowered_for_non_root_user(self, mock_setuid,
+ def test_privileges_are_lowered_for_non_root_user(self, mock_geteuid,
+ mock_seteuid,
mock_getpwnam):
pw_entry = mock.MagicMock()
pw_entry.pw_uid = 1001
mock_getpwnam.return_value = pw_entry
+ mock_geteuid.return_value = 0
+ calls = [mock.call(1001), mock.call(0)]
with mock.patch('subprocess.Popen') as mock_popen:
- command = '/bin/command --option=value arg1 arg2'
+ command = ['/bin/command', '--option=value', 'arg1', 'arg2']
cmd = cfn_helper.CommandRunner(command)
cmd.run(user='nonroot')
+ self.assertTrue(mock_geteuid.called)
mock_getpwnam.assert_called_once_with('nonroot')
- mock_setuid.assert_called_once_with(1001)
+ mock_seteuid.assert_has_calls(calls)
self.assertTrue(mock_popen.called)
- @mock.patch.object(cfn_helper.pwd, 'getpwnam')
- @mock.patch.object(cfn_helper.os, 'setuid')
- def test_run_returns_when_cannot_set_privileges(self, mock_setuid,
+ def test_run_returns_when_cannot_set_privileges(self, mock_geteuid,
+ mock_seteuid,
mock_getpwnam):
- mock_setuid.side_effect = Exception('[Error 1] Permission Denied')
+ msg = '[Error 1] Permission Denied'
+ mock_seteuid.side_effect = Exception(msg)
with mock.patch('subprocess.Popen') as mock_popen:
- command = '/bin/command2'
+ command = ['/bin/command2']
cmd = cfn_helper.CommandRunner(command)
cmd.run(user='nonroot')
self.assertTrue(mock_getpwnam.called)
- self.assertTrue(mock_setuid.called)
+ self.assertTrue(mock_seteuid.called)
self.assertFalse(mock_popen.called)
self.assertEqual(126, cmd.status)
+ self.assertEqual(msg, cmd.stderr)
+
+ def test_privileges_are_restored_for_command_failure(self, mock_geteuid,
+ mock_seteuid,
+ mock_getpwnam):
+ pw_entry = mock.MagicMock()
+ pw_entry.pw_uid = 1001
+ mock_getpwnam.return_value = pw_entry
+ mock_geteuid.return_value = 0
+ calls = [mock.call(1001), mock.call(0)]
+ with mock.patch('subprocess.Popen') as mock_popen:
+ mock_popen.side_effect = ValueError('Something wrong')
+ command = ['/bin/command', '--option=value', 'arg1', 'arg2']
+ cmd = cfn_helper.CommandRunner(command)
+ self.assertRaises(ValueError, cmd.run, user='nonroot')
+ self.assertTrue(mock_geteuid.called)
+ mock_getpwnam.assert_called_once_with('nonroot')
+ mock_seteuid.assert_has_calls(calls)
+ self.assertTrue(mock_popen.called)
+@mock.patch.object(cfn_helper, 'controlled_privileges')
class TestPackages(testtools.TestCase):
- def test_yum_install(self):
+ def test_yum_install(self, mock_cp):
def returns(*args, **kwargs):
- if (args[0][0] == 'rpm' and args[0][1] == '-q'):
+ if args[0][0] == 'rpm' and args[0][1] == '-q':
return FakePOpen(returncode=1)
else:
return FakePOpen(returncode=0)
- calls = ['which yum']
+ calls = [['which', 'yum']]
for pack in ('httpd', 'wordpress', 'mysql-server'):
- calls.append('rpm -q %s' % pack)
- calls.append('yum -y --showduplicates list available %s' % pack)
+ calls.append(['rpm', '-q', pack])
+ calls.append(['yum', '-y', '--showduplicates', 'list',
+ 'available', pack])
calls = popen_root_calls(calls)
packages = {
@@ -143,7 +161,7 @@ class TestPackages(testtools.TestCase):
cfn_helper.PackagesHandler(packages).apply_packages()
mock_popen.assert_has_calls(calls, any_order=True)
- def test_dnf_install_yum_unavailable(self):
+ def test_dnf_install_yum_unavailable(self, mock_cp):
def returns(*args, **kwargs):
if ((args[0][0] == 'rpm' and args[0][1] == '-q')
@@ -152,10 +170,11 @@ class TestPackages(testtools.TestCase):
else:
return FakePOpen(returncode=0)
- calls = ['which yum']
+ calls = [['which', 'yum']]
for pack in ('httpd', 'wordpress', 'mysql-server'):
- calls.append('rpm -q %s' % pack)
- calls.append('dnf -y --showduplicates list available %s' % pack)
+ calls.append(['rpm', '-q', pack])
+ calls.append(['dnf', '-y', '--showduplicates', 'list',
+ 'available', pack])
calls = popen_root_calls(calls)
packages = {
@@ -171,18 +190,19 @@ class TestPackages(testtools.TestCase):
cfn_helper.PackagesHandler(packages).apply_packages()
mock_popen.assert_has_calls(calls, any_order=True)
- def test_dnf_install(self):
+ def test_dnf_install(self, mock_cp):
def returns(*args, **kwargs):
- if (args[0][0] == 'rpm' and args[0][1] == '-q'):
+ if args[0][0] == 'rpm' and args[0][1] == '-q':
return FakePOpen(returncode=1)
else:
return FakePOpen(returncode=0)
calls = []
for pack in ('httpd', 'wordpress', 'mysql-server'):
- calls.append('rpm -q %s' % pack)
- calls.append('dnf -y --showduplicates list available %s' % pack)
+ calls.append(['rpm', '-q', pack])
+ calls.append(['dnf', '-y', '--showduplicates', 'list',
+ 'available', pack])
calls = popen_root_calls(calls)
packages = {
@@ -198,18 +218,18 @@ class TestPackages(testtools.TestCase):
cfn_helper.PackagesHandler(packages).apply_packages()
mock_popen.assert_has_calls(calls, any_order=True)
- def test_zypper_install(self):
+ def test_zypper_install(self, mock_cp):
def returns(*args, **kwargs):
- if (args[0][0] == 'rpm' and args[0][1] == '-q'):
+ if args[0][0].startswith('rpm') and args[0][1].startswith('-q'):
return FakePOpen(returncode=1)
else:
return FakePOpen(returncode=0)
calls = []
for pack in ('httpd', 'wordpress', 'mysql-server'):
- calls.append('rpm -q %s' % pack)
- calls.append('zypper -n --no-refresh search %s' % pack)
+ calls.append(['rpm', '-q', pack])
+ calls.append(['zypper', '-n', '--no-refresh', 'search', pack])
calls = popen_root_calls(calls)
packages = {
@@ -225,7 +245,7 @@ class TestPackages(testtools.TestCase):
cfn_helper.PackagesHandler(packages).apply_packages()
mock_popen.assert_has_calls(calls, any_order=True)
- def test_apt_install(self):
+ def test_apt_install(self, mock_cp):
packages = {
"apt": {
"mysql-server": [],
@@ -240,47 +260,57 @@ class TestPackages(testtools.TestCase):
self.assertTrue(mock_popen.called)
+@mock.patch.object(cfn_helper, 'controlled_privileges')
class TestServicesHandler(testtools.TestCase):
- def test_services_handler_systemd(self):
+ def test_services_handler_systemd(self, mock_cp):
calls = []
returns = []
# apply_services
- calls.append('/bin/systemctl enable httpd.service')
+ calls.append(['/bin/systemctl', 'enable', 'httpd.service'])
returns.append(FakePOpen())
- calls.append('/bin/systemctl status httpd.service')
+ calls.append(['/bin/systemctl', 'status', 'httpd.service'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/bin/systemctl start httpd.service')
+ calls.append(['/bin/systemctl', 'start', 'httpd.service'])
returns.append(FakePOpen())
- calls.append('/bin/systemctl enable mysqld.service')
+ calls.append(['/bin/systemctl', 'enable', 'mysqld.service'])
returns.append(FakePOpen())
- calls.append('/bin/systemctl status mysqld.service')
+ calls.append(['/bin/systemctl', 'status', 'mysqld.service'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/bin/systemctl start mysqld.service')
+ calls.append(['/bin/systemctl', 'start', 'mysqld.service'])
returns.append(FakePOpen())
# monitor_services not running
- calls.append('/bin/systemctl status httpd.service')
+ calls.append(['/bin/systemctl', 'status', 'httpd.service'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/bin/systemctl start httpd.service')
+ calls.append(['/bin/systemctl', 'start', 'httpd.service'])
returns.append(FakePOpen())
- calls.append('/bin/services_restarted')
+
+ calls = popen_root_calls(calls)
+
+ calls.extend(popen_root_calls(['/bin/services_restarted'], shell=True))
returns.append(FakePOpen())
- calls.append('/bin/systemctl status mysqld.service')
+
+ calls.extend(popen_root_calls([['/bin/systemctl', 'status',
+ 'mysqld.service']]))
returns.append(FakePOpen(returncode=-1))
- calls.append('/bin/systemctl start mysqld.service')
+ calls.extend(popen_root_calls([['/bin/systemctl', 'start',
+ 'mysqld.service']]))
returns.append(FakePOpen())
- calls.append('/bin/services_restarted')
+
+ calls.extend(popen_root_calls(['/bin/services_restarted'], shell=True))
returns.append(FakePOpen())
# monitor_services running
- calls.append('/bin/systemctl status httpd.service')
+ calls.extend(popen_root_calls([['/bin/systemctl', 'status',
+ 'httpd.service']]))
returns.append(FakePOpen())
- calls.append('/bin/systemctl status mysqld.service')
+ calls.extend(popen_root_calls([['/bin/systemctl', 'status',
+ 'mysqld.service']]))
returns.append(FakePOpen())
- calls = popen_root_calls(calls)
+ #calls = popen_root_calls(calls)
services = {
"systemd": {
@@ -312,16 +342,16 @@ class TestServicesHandler(testtools.TestCase):
mock_popen.assert_has_calls(calls, any_order=True)
mock_exists.assert_called_with('/bin/systemctl')
- def test_services_handler_systemd_disabled(self):
+ def test_services_handler_systemd_disabled(self, mock_cp):
calls = []
# apply_services
- calls.append('/bin/systemctl disable httpd.service')
- calls.append('/bin/systemctl status httpd.service')
- calls.append('/bin/systemctl stop httpd.service')
- calls.append('/bin/systemctl disable mysqld.service')
- calls.append('/bin/systemctl status mysqld.service')
- calls.append('/bin/systemctl stop mysqld.service')
+ calls.append(['/bin/systemctl', 'disable', 'httpd.service'])
+ calls.append(['/bin/systemctl', 'status', 'httpd.service'])
+ calls.append(['/bin/systemctl', 'stop', 'httpd.service'])
+ calls.append(['/bin/systemctl', 'disable', 'mysqld.service'])
+ calls.append(['/bin/systemctl', 'status', 'mysqld.service'])
+ calls.append(['/bin/systemctl', 'stop', 'mysqld.service'])
calls = popen_root_calls(calls)
services = {
@@ -347,7 +377,7 @@ class TestServicesHandler(testtools.TestCase):
mock_popen.assert_has_calls(calls, any_order=True)
mock_exists.assert_called_with('/bin/systemctl')
- def test_services_handler_sysv_service_chkconfig(self):
+ def test_services_handler_sysv_service_chkconfig(self, mock_cp):
def exists(*args, **kwargs):
return args[0] != '/bin/systemctl'
@@ -356,27 +386,28 @@ class TestServicesHandler(testtools.TestCase):
returns = []
# apply_services
- calls.append('/sbin/chkconfig httpd on')
+ calls.append(['/sbin/chkconfig', 'httpd', 'on'])
returns.append(FakePOpen())
- calls.append('/sbin/service httpd status')
+ calls.append(['/sbin/service', 'httpd', 'status'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/sbin/service httpd start')
+ calls.append(['/sbin/service', 'httpd', 'start'])
returns.append(FakePOpen())
# monitor_services not running
- calls.append('/sbin/service httpd status')
+ calls.append(['/sbin/service', 'httpd', 'status'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/sbin/service httpd start')
+ calls.append(['/sbin/service', 'httpd', 'start'])
returns.append(FakePOpen())
- calls.append('/bin/services_restarted')
+
+ calls = popen_root_calls(calls)
+
+ calls.extend(popen_root_calls(['/bin/services_restarted'], shell=True))
returns.append(FakePOpen())
# monitor_services running
- calls.append('/sbin/service httpd status')
+ calls.extend(popen_root_calls([['/sbin/service', 'httpd', 'status']]))
returns.append(FakePOpen())
- calls = popen_root_calls(calls)
-
services = {
"sysvinit": {
"httpd": {"enabled": "true", "ensureRunning": "true"}
@@ -407,16 +438,16 @@ class TestServicesHandler(testtools.TestCase):
mock_exists.assert_any_call('/sbin/service')
mock_exists.assert_any_call('/sbin/chkconfig')
- def test_services_handler_sysv_disabled_service_chkconfig(self):
+ def test_services_handler_sysv_disabled_service_chkconfig(self, mock_cp):
def exists(*args, **kwargs):
return args[0] != '/bin/systemctl'
calls = []
# apply_services
- calls.append('/sbin/chkconfig httpd off')
- calls.append('/sbin/service httpd status')
- calls.append('/sbin/service httpd stop')
+ calls.append(['/sbin/chkconfig', 'httpd', 'off'])
+ calls.append(['/sbin/service', 'httpd', 'status'])
+ calls.append(['/sbin/service', 'httpd', 'stop'])
calls = popen_root_calls(calls)
@@ -445,31 +476,35 @@ class TestServicesHandler(testtools.TestCase):
mock_exists.assert_any_call('/sbin/service')
mock_exists.assert_any_call('/sbin/chkconfig')
- def test_services_handler_sysv_systemctl(self):
+ def test_services_handler_sysv_systemctl(self, mock_cp):
calls = []
returns = []
# apply_services
- calls.append('/bin/systemctl enable httpd.service')
+ calls.append(['/bin/systemctl', 'enable', 'httpd.service'])
returns.append(FakePOpen())
- calls.append('/bin/systemctl status httpd.service')
+ calls.append(['/bin/systemctl', 'status', 'httpd.service'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/bin/systemctl start httpd.service')
+ calls.append(['/bin/systemctl', 'start', 'httpd.service'])
returns.append(FakePOpen())
# monitor_services not running
- calls.append('/bin/systemctl status httpd.service')
+ calls.append(['/bin/systemctl', 'status', 'httpd.service'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/bin/systemctl start httpd.service')
- returns.append(FakePOpen())
- calls.append('/bin/services_restarted')
+ calls.append(['/bin/systemctl', 'start', 'httpd.service'])
returns.append(FakePOpen())
- # monitor_services running
- calls.append('/bin/systemctl status httpd.service')
+ shell_calls = []
+ shell_calls.append('/bin/services_restarted')
returns.append(FakePOpen())
calls = popen_root_calls(calls)
+ calls.extend(popen_root_calls(shell_calls, shell=True))
+
+ # monitor_services running
+ calls.extend(popen_root_calls([['/bin/systemctl', 'status',
+ 'httpd.service']]))
+ returns.append(FakePOpen())
services = {
"sysvinit": {
@@ -499,13 +534,13 @@ class TestServicesHandler(testtools.TestCase):
mock_popen.assert_has_calls(calls)
mock_exists.assert_called_with('/bin/systemctl')
- def test_services_handler_sysv_disabled_systemctl(self):
+ def test_services_handler_sysv_disabled_systemctl(self, mock_cp):
calls = []
# apply_services
- calls.append('/bin/systemctl disable httpd.service')
- calls.append('/bin/systemctl status httpd.service')
- calls.append('/bin/systemctl stop httpd.service')
+ calls.append(['/bin/systemctl', 'disable', 'httpd.service'])
+ calls.append(['/bin/systemctl', 'status', 'httpd.service'])
+ calls.append(['/bin/systemctl', 'stop', 'httpd.service'])
calls = popen_root_calls(calls)
@@ -532,31 +567,35 @@ class TestServicesHandler(testtools.TestCase):
mock_popen.assert_has_calls(calls)
mock_exists.assert_called_with('/bin/systemctl')
- def test_services_handler_sysv_service_updaterc(self):
+ def test_services_handler_sysv_service_updaterc(self, mock_cp):
calls = []
returns = []
# apply_services
- calls.append('/usr/sbin/update-rc.d httpd enable')
+ calls.append(['/usr/sbin/update-rc.d', 'httpd', 'enable'])
returns.append(FakePOpen())
- calls.append('/usr/sbin/service httpd status')
+ calls.append(['/usr/sbin/service', 'httpd', 'status'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/usr/sbin/service httpd start')
+ calls.append(['/usr/sbin/service', 'httpd', 'start'])
returns.append(FakePOpen())
# monitor_services not running
- calls.append('/usr/sbin/service httpd status')
+ calls.append(['/usr/sbin/service', 'httpd', 'status'])
returns.append(FakePOpen(returncode=-1))
- calls.append('/usr/sbin/service httpd start')
- returns.append(FakePOpen())
- calls.append('/bin/services_restarted')
+ calls.append(['/usr/sbin/service', 'httpd', 'start'])
returns.append(FakePOpen())
- # monitor_services running
- calls.append('/usr/sbin/service httpd status')
+ shell_calls = []
+ shell_calls.append('/bin/services_restarted')
returns.append(FakePOpen())
calls = popen_root_calls(calls)
+ calls.extend(popen_root_calls(shell_calls, shell=True))
+
+ # monitor_services running
+ calls.extend(popen_root_calls([['/usr/sbin/service', 'httpd',
+ 'status']]))
+ returns.append(FakePOpen())
services = {
"sysvinit": {
@@ -588,16 +627,16 @@ class TestServicesHandler(testtools.TestCase):
mock_exists.assert_any_call('/sbin/service')
mock_exists.assert_any_call('/sbin/chkconfig')
- def test_services_handler_sysv_disabled_service_updaterc(self):
+ def test_services_handler_sysv_disabled_service_updaterc(self, mock_cp):
calls = []
returns = []
# apply_services
- calls.append('/usr/sbin/update-rc.d httpd disable')
+ calls.append(['/usr/sbin/update-rc.d', 'httpd', 'disable'])
returns.append(FakePOpen())
- calls.append('/usr/sbin/service httpd status')
+ calls.append(['/usr/sbin/service', 'httpd', 'status'])
returns.append(FakePOpen())
- calls.append('/usr/sbin/service httpd stop')
+ calls.append(['/usr/sbin/service', 'httpd', 'stop'])
returns.append(FakePOpen())
calls = popen_root_calls(calls)
@@ -659,7 +698,8 @@ interval=120''' % fcreds.name).encode('UTF-8'))
self.assertIn('invalid credentials file', str(e))
fcreds.close()
- def test_hup_config(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_hup_config(self, mock_cp):
hooks_conf = tempfile.NamedTemporaryFile()
def write_hook_conf(f, name, triggers, path, action):
@@ -734,11 +774,11 @@ interval=120''' % fcreds.name).encode('UTF-8'))
' root, /bin/hook3}', str(hooks[3]))
calls = []
- calls.append('/bin/cfn-http-restarted')
- calls.append('/bin/hook1')
- calls.append('/bin/hook2')
- calls.append('/bin/hook3')
- calls = popen_root_calls(calls)
+ calls.extend(popen_root_calls(['/bin/cfn-http-restarted'], shell=True))
+ calls.extend(popen_root_calls(['/bin/hook1'], shell=True))
+ calls.extend(popen_root_calls(['/bin/hook2'], shell=True))
+ calls.extend(popen_root_calls(['/bin/hook3'], shell=True))
+ #calls = popen_root_calls(calls)
with mock.patch('subprocess.Popen') as mock_popen:
mock_popen.return_value = FakePOpen('All good')
@@ -1070,7 +1110,8 @@ class TestMetadataRetrieve(testtools.TestCase):
self.assertEqual(meta_in, meta_out)
- def test_nova_meta_curl(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_nova_meta_curl(self, mock_cp):
url = 'http://169.254.169.254/openstack/2012-08-10/meta_data.json'
temp_home = tempfile.mkdtemp()
cache_path = os.path.join(temp_home, 'meta_data.json')
@@ -1103,9 +1144,10 @@ class TestMetadataRetrieve(testtools.TestCase):
meta_out = md.get_nova_meta(cache_path=cache_path)
self.assertEqual(meta_in, meta_out)
mock_popen.assert_has_calls(
- popen_root_calls(['curl -o %s %s' % (cache_path, url)]))
+ popen_root_calls([['curl', '-o', cache_path, url]]))
- def test_nova_meta_curl_corrupt(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_nova_meta_curl_corrupt(self, mock_cp):
url = 'http://169.254.169.254/openstack/2012-08-10/meta_data.json'
temp_home = tempfile.mkdtemp()
cache_path = os.path.join(temp_home, 'meta_data.json')
@@ -1131,9 +1173,10 @@ class TestMetadataRetrieve(testtools.TestCase):
meta_out = md.get_nova_meta(cache_path=cache_path)
self.assertEqual(None, meta_out)
mock_popen.assert_has_calls(
- popen_root_calls(['curl -o %s %s' % (cache_path, url)]))
+ popen_root_calls([['curl', '-o', cache_path, url]]))
- def test_nova_meta_curl_failed(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_nova_meta_curl_failed(self, mock_cp):
url = 'http://169.254.169.254/openstack/2012-08-10/meta_data.json'
temp_home = tempfile.mkdtemp()
cache_path = os.path.join(temp_home, 'meta_data.json')
@@ -1149,7 +1192,7 @@ class TestMetadataRetrieve(testtools.TestCase):
meta_out = md.get_nova_meta(cache_path=cache_path)
self.assertEqual(None, meta_out)
mock_popen.assert_has_calls(
- popen_root_calls(['curl -o %s %s' % (cache_path, url)]))
+ popen_root_calls([['curl', '-o', cache_path, url]]))
def test_get_tags(self):
fake_tags = {'foo': 'fee',
@@ -1209,7 +1252,8 @@ class TestCfnInit(testtools.TestCase):
md.cfn_init()
self.assertThat(foo_file.name, ttm.FileContains('bar'))
- def test_cfn_init_with_ignore_errors_false(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_cfn_init_with_ignore_errors_false(self, mock_cp):
md_data = {"AWS::CloudFormation::Init": {"config": {"commands": {
"00_foo": {"command": "/bin/command1",
"ignoreErrors": "false"}}}}}
@@ -1219,16 +1263,18 @@ class TestCfnInit(testtools.TestCase):
self.assertTrue(
md.retrieve(meta_str=md_data, last_path=self.last_file))
self.assertRaises(cfn_helper.CommandsHandlerRunError, md.cfn_init)
- mock_popen.assert_has_calls(popen_root_calls(['/bin/command1']))
+ mock_popen.assert_has_calls(popen_root_calls(['/bin/command1'],
+ shell=True))
- def test_cfn_init_with_ignore_errors_true(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_cfn_init_with_ignore_errors_true(self, mock_cp):
calls = []
returns = []
- calls.append('/bin/command1')
+ calls.extend(popen_root_calls(['/bin/command1'], shell=True))
returns.append(FakePOpen('Doing something', 'error', -1))
- calls.append('/bin/command2')
+ calls.extend(popen_root_calls(['/bin/command2'], shell=True))
returns.append(FakePOpen('All good'))
- calls = popen_root_calls(calls)
+ #calls = popen_root_calls(calls)
md_data = {"AWS::CloudFormation::Init": {"config": {"commands": {
"00_foo": {"command": "/bin/command1",
@@ -1257,7 +1303,7 @@ class TestSourcesHandler(testtools.TestCase):
sources = {dest: url}
td = os.path.dirname(end_file)
er = "mkdir -p '%s'; cd '%s'; curl -s '%s' | gunzip | tar -xvf -"
- calls = popen_root_calls([er % (dest, dest, url)])
+ calls = popen_root_calls([er % (dest, dest, url)], shell=True)
with mock.patch.object(tempfile, 'mkdtemp') as mock_mkdtemp:
mock_mkdtemp.return_value = td
@@ -1268,26 +1314,28 @@ class TestSourcesHandler(testtools.TestCase):
mock_popen.assert_has_calls(calls)
mock_mkdtemp.assert_called_with()
- def test_apply_sources_github(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_apply_sources_github(self, mock_cp):
url = "https://github.com/NoSuchProject/tarball/NoSuchTarball"
dest = tempfile.mkdtemp()
self.addCleanup(os.rmdir, dest)
sources = {dest: url}
er = "mkdir -p '%s'; cd '%s'; curl -s '%s' | gunzip | tar -xvf -"
- calls = popen_root_calls([er % (dest, dest, url)])
+ calls = popen_root_calls([er % (dest, dest, url)], shell=True)
with mock.patch('subprocess.Popen') as mock_popen:
mock_popen.return_value = FakePOpen('Curl good')
sh = cfn_helper.SourcesHandler(sources)
sh.apply_sources()
mock_popen.assert_has_calls(calls)
- def test_apply_sources_general(self):
+ @mock.patch.object(cfn_helper, 'controlled_privileges')
+ def test_apply_sources_general(self, mock_cp):
url = "https://website.no.existe/a/b/c/file.tar.gz"
dest = tempfile.mkdtemp()
self.addCleanup(os.rmdir, dest)
sources = {dest: url}
er = "mkdir -p '%s'; cd '%s'; curl -s '%s' | gunzip | tar -xvf -"
- calls = popen_root_calls([er % (dest, dest, url)])
+ calls = popen_root_calls([er % (dest, dest, url)], shell=True)
with mock.patch('subprocess.Popen') as mock_popen:
mock_popen.return_value = FakePOpen('Curl good')
sh = cfn_helper.SourcesHandler(sources)