Command validation
This commit includes a validation in Command.run and Command.call in order to verify the existance of the command before running it. It case it is not found in the specified environment it raises a KiwiCommandNotFound Exception.
This commit is contained in:
parent
d8df88f23a
commit
0fdd209e73
@ -26,7 +26,7 @@ from collections import namedtuple
|
||||
from builtins import bytes
|
||||
|
||||
# project
|
||||
from .exceptions import KiwiCommandError
|
||||
from .exceptions import KiwiCommandError, KiwiCommandNotFound
|
||||
|
||||
|
||||
class Command(object):
|
||||
@ -60,6 +60,10 @@ class Command(object):
|
||||
environment = os.environ
|
||||
if custom_env:
|
||||
environment = custom_env
|
||||
if not self._command_exists(command[0], environment):
|
||||
raise KiwiCommandNotFound(
|
||||
'Command {} not found in the environment'.format(command[0])
|
||||
)
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
@ -117,6 +121,10 @@ class Command(object):
|
||||
environment = os.environ
|
||||
if custom_env:
|
||||
environment = custom_env
|
||||
if not self._command_exists(command[0], environment):
|
||||
raise KiwiCommandNotFound(
|
||||
'Command {} not found in the environment'.format(command[0])
|
||||
)
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
@ -165,3 +173,24 @@ class Command(object):
|
||||
error_available=error_available(),
|
||||
process=process
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _command_exists(self, command, env):
|
||||
"""
|
||||
Tests if the given command is executable for the specified
|
||||
environment. If the specified environment does not include
|
||||
'PATH' variable, the method raises a KiwiCommandError
|
||||
exception
|
||||
|
||||
:param string command: command to validate
|
||||
:param list env: custom os.environ
|
||||
|
||||
:return: True if the command is found, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
if 'PATH' not in env:
|
||||
raise KiwiCommandError('No PATH variable in environment')
|
||||
return any(
|
||||
os.access(os.path.join(path, command), os.X_OK)
|
||||
for path in env['PATH'].split(os.pathsep)
|
||||
)
|
||||
|
||||
@ -155,6 +155,13 @@ class KiwiCommandError(KiwiError):
|
||||
"""
|
||||
|
||||
|
||||
class KiwiCommandNotFound(KiwiCommandError):
|
||||
"""
|
||||
Exception raised if any executable command cannot be found in
|
||||
the evironment PATH variable.
|
||||
"""
|
||||
|
||||
|
||||
class KiwiCommandNotLoaded(KiwiError):
|
||||
"""
|
||||
Exception raised if a kiwi command task module could not be
|
||||
|
||||
@ -37,7 +37,7 @@ from .exceptions import (
|
||||
KiwiDescriptionInvalid,
|
||||
KiwiDataStructureError,
|
||||
KiwiDescriptionConflict,
|
||||
KiwiCommandError
|
||||
KiwiCommandNotFound
|
||||
)
|
||||
|
||||
|
||||
@ -124,11 +124,13 @@ class XMLDescription(object):
|
||||
['jing', Defaults.get_schema_file(), self.description_xslt_processed.name],
|
||||
raise_on_error=False
|
||||
)
|
||||
except KiwiCommandError:
|
||||
log.info('Failed running jing command')
|
||||
except KiwiCommandNotFound as e:
|
||||
log.info('A detailed schema validation failure report requires jing to be installed')
|
||||
log.info(
|
||||
'%s: %s: %s' % ('jing', type(e).__name__, format(e))
|
||||
)
|
||||
return
|
||||
log.info('Schema validation failed. Jing report below:')
|
||||
log.info('Schema validation failed. See jing report:')
|
||||
log.info('--> ' + cmd.output)
|
||||
|
||||
def _parse(self):
|
||||
|
||||
@ -8,36 +8,53 @@ import os
|
||||
|
||||
from .test_helper import *
|
||||
|
||||
from kiwi.exceptions import KiwiCommandError
|
||||
from kiwi.exceptions import KiwiCommandError, KiwiCommandNotFound
|
||||
from kiwi.command import Command
|
||||
|
||||
|
||||
class TestCommand(object):
|
||||
@raises(KiwiCommandError)
|
||||
@patch('os.access')
|
||||
@patch('subprocess.Popen')
|
||||
def test_run_raises_error(self, mock_popen):
|
||||
def test_run_raises_error(self, mock_popen, mock_access):
|
||||
mock_process = mock.Mock()
|
||||
mock_process.communicate = mock.Mock(
|
||||
return_value=[str.encode('stdout'), str.encode('stderr')]
|
||||
)
|
||||
mock_process.returncode = 1
|
||||
mock_popen.return_value = mock_process
|
||||
mock_access.return_value = True
|
||||
Command.run(['command', 'args'])
|
||||
|
||||
@raises(KiwiCommandError)
|
||||
@patch('os.access')
|
||||
@patch('subprocess.Popen')
|
||||
def test_run_does_not_raise_error(self, mock_popen):
|
||||
def test_run_failure(self, mock_popen, mock_access):
|
||||
mock_popen.side_effect = KiwiCommandError('Run failure')
|
||||
mock_access.return_value = True
|
||||
Command.run(['command', 'args'])
|
||||
|
||||
@raises(KiwiCommandError)
|
||||
def test_run_invalid_environment(self):
|
||||
Command.run(['command', 'args'], 'invalidEnvironment')
|
||||
|
||||
@patch('os.access')
|
||||
@patch('subprocess.Popen')
|
||||
def test_run_does_not_raise_error(self, mock_popen, mock_access):
|
||||
mock_process = mock.Mock()
|
||||
mock_process.communicate = mock.Mock(
|
||||
return_value=[str.encode('stdout'), str.encode('')]
|
||||
)
|
||||
mock_process.returncode = 1
|
||||
mock_popen.return_value = mock_process
|
||||
mock_access.return_value = True
|
||||
result = Command.run(['command', 'args'], os.environ, False)
|
||||
assert result.error == '(no output on stderr)'
|
||||
assert result.output == 'stdout'
|
||||
|
||||
@patch('os.access')
|
||||
@patch('subprocess.Popen')
|
||||
def test_run(self, mock_popen):
|
||||
def test_run(self, mock_popen, mock_access):
|
||||
command_run = namedtuple(
|
||||
'command', ['output', 'error', 'returncode']
|
||||
)
|
||||
@ -52,22 +69,25 @@ class TestCommand(object):
|
||||
)
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
mock_access.return_value = True
|
||||
assert Command.run(['command', 'args']) == run_result
|
||||
|
||||
@raises(KiwiCommandError)
|
||||
@raises(KiwiCommandNotFound)
|
||||
def test_run_command_does_not_exist(self):
|
||||
Command.run(['does-not-exist'])
|
||||
|
||||
@raises(KiwiCommandError)
|
||||
@raises(KiwiCommandNotFound)
|
||||
def test_call_command_does_not_exist(self):
|
||||
Command.call(['does-not-exist'], os.environ)
|
||||
|
||||
@patch('os.access')
|
||||
@patch('subprocess.Popen')
|
||||
@patch('select.select')
|
||||
def test_call(self, mock_select, mock_popen):
|
||||
def test_call(self, mock_select, mock_popen, mock_access):
|
||||
mock_select.return_value = [True, False, False]
|
||||
mock_process = mock.Mock()
|
||||
mock_popen.return_value = mock_process
|
||||
mock_access.return_value = True
|
||||
command_call = namedtuple(
|
||||
'command', [
|
||||
'output', 'output_available',
|
||||
@ -81,3 +101,11 @@ class TestCommand(object):
|
||||
assert call.output == mock_process.stdout
|
||||
assert call.error == mock_process.stderr
|
||||
assert call.process == mock_process
|
||||
|
||||
@raises(KiwiCommandError)
|
||||
@patch('os.access')
|
||||
@patch('subprocess.Popen')
|
||||
def test_call_failure(self,mock_popen, mock_access):
|
||||
mock_popen.side_effect = KiwiCommandError('Call failure')
|
||||
mock_access.return_value = True
|
||||
call = Command.call(['command', 'args'])
|
||||
|
||||
@ -11,7 +11,7 @@ from kiwi.exceptions import (
|
||||
KiwiDescriptionInvalid,
|
||||
KiwiDataStructureError,
|
||||
KiwiDescriptionConflict,
|
||||
KiwiCommandError
|
||||
KiwiCommandNotFound
|
||||
)
|
||||
from kiwi.xml_description import XMLDescription
|
||||
|
||||
@ -138,7 +138,7 @@ class TestSchema(object):
|
||||
return_value=False
|
||||
)
|
||||
mock_relax.return_value = mock_validate
|
||||
mock_command.side_effect = KiwiCommandError('No jing command')
|
||||
mock_command.side_effect = KiwiCommandNotFound('No jing command')
|
||||
self.description_from_data.load()
|
||||
|
||||
@raises(KiwiDataStructureError)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user