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:
David Cassany 2016-08-05 18:38:27 +02:00
parent d8df88f23a
commit 0fdd209e73
5 changed files with 80 additions and 14 deletions

View File

@ -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)
)

View File

@ -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

View File

@ -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):

View File

@ -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'])

View File

@ -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)