Added template rendering to shell_command component (#2268)
* Added template rendering to `shell_command` component * Security upgrades to template rendering in shell_command. * Added new unit tests for shell_command templates. Better failure when template is invalid in shell_commandpull/2326/head
parent
b646accf87
commit
2882f05f2c
|
@ -9,6 +9,8 @@ import subprocess
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.helpers import template
|
||||||
|
from homeassistant.exceptions import TemplateError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
DOMAIN = 'shell_command'
|
DOMAIN = 'shell_command'
|
||||||
|
@ -30,14 +32,38 @@ def setup(hass, config):
|
||||||
|
|
||||||
def service_handler(call):
|
def service_handler(call):
|
||||||
"""Execute a shell command service."""
|
"""Execute a shell command service."""
|
||||||
|
cmd = conf[call.service]
|
||||||
|
cmd, shell = _parse_command(hass, cmd, call.data)
|
||||||
|
if cmd is None:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
subprocess.call(conf[call.service], shell=True,
|
subprocess.call(cmd, shell=shell,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL)
|
stderr=subprocess.DEVNULL)
|
||||||
except subprocess.SubprocessError:
|
except subprocess.SubprocessError:
|
||||||
_LOGGER.exception('Error running command')
|
_LOGGER.exception('Error running command: %s', cmd)
|
||||||
|
|
||||||
for name in conf.keys():
|
for name in conf.keys():
|
||||||
hass.services.register(DOMAIN, name, service_handler,
|
hass.services.register(DOMAIN, name, service_handler,
|
||||||
schema=SHELL_COMMAND_SCHEMA)
|
schema=SHELL_COMMAND_SCHEMA)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_command(hass, cmd, variables):
|
||||||
|
"""Parse command and fill in any template arguments if necessary."""
|
||||||
|
cmds = cmd.split()
|
||||||
|
prog = cmds[0]
|
||||||
|
args = ' '.join(cmds[1:])
|
||||||
|
try:
|
||||||
|
rendered_args = template.render(hass, args, variables=variables)
|
||||||
|
except TemplateError as ex:
|
||||||
|
_LOGGER.exception('Error rendering command template: %s', ex)
|
||||||
|
return None, None
|
||||||
|
if rendered_args == args:
|
||||||
|
# no template used. default behavior
|
||||||
|
shell = True
|
||||||
|
else:
|
||||||
|
# template used. Must break into list and use shell=False for security
|
||||||
|
cmd = [prog] + rendered_args.split()
|
||||||
|
shell = False
|
||||||
|
return cmd, shell
|
||||||
|
|
|
@ -51,6 +51,30 @@ class TestShellCommand(unittest.TestCase):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_template_render_no_template(self):
|
||||||
|
"""Ensure shell_commands without templates get rendered properly."""
|
||||||
|
cmd, shell = shell_command._parse_command(self.hass, 'ls /bin', {})
|
||||||
|
self.assertTrue(shell)
|
||||||
|
self.assertEqual(cmd, 'ls /bin')
|
||||||
|
|
||||||
|
def test_template_render(self):
|
||||||
|
"""Ensure shell_commands with templates get rendered properly."""
|
||||||
|
self.hass.states.set('sensor.test_state', 'Works')
|
||||||
|
cmd, shell = shell_command._parse_command(
|
||||||
|
self.hass,
|
||||||
|
'ls /bin {{ states.sensor.test_state.state }}', {}
|
||||||
|
)
|
||||||
|
self.assertFalse(shell, False)
|
||||||
|
self.assertEqual(cmd[-1], 'Works')
|
||||||
|
|
||||||
|
def test_invalid_template_fails(self):
|
||||||
|
"""Test that shell_commands with invalid templates fail."""
|
||||||
|
cmd, _shell = shell_command._parse_command(
|
||||||
|
self.hass,
|
||||||
|
'ls /bin {{ states. .test_state.state }}', {}
|
||||||
|
)
|
||||||
|
self.assertEqual(cmd, None)
|
||||||
|
|
||||||
@patch('homeassistant.components.shell_command.subprocess.call',
|
@patch('homeassistant.components.shell_command.subprocess.call',
|
||||||
side_effect=SubprocessError)
|
side_effect=SubprocessError)
|
||||||
@patch('homeassistant.components.shell_command._LOGGER.error')
|
@patch('homeassistant.components.shell_command._LOGGER.error')
|
||||||
|
|
Loading…
Reference in New Issue