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_command
pull/2326/head
Nick Touran 2016-06-18 09:57:18 -07:00 committed by Paulus Schoutsen
parent b646accf87
commit 2882f05f2c
2 changed files with 52 additions and 2 deletions

View File

@ -9,6 +9,8 @@ import subprocess
import voluptuous as vol
from homeassistant.helpers import template
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
DOMAIN = 'shell_command'
@ -30,14 +32,38 @@ def setup(hass, config):
def service_handler(call):
"""Execute a shell command service."""
cmd = conf[call.service]
cmd, shell = _parse_command(hass, cmd, call.data)
if cmd is None:
return
try:
subprocess.call(conf[call.service], shell=True,
subprocess.call(cmd, shell=shell,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except subprocess.SubprocessError:
_LOGGER.exception('Error running command')
_LOGGER.exception('Error running command: %s', cmd)
for name in conf.keys():
hass.services.register(DOMAIN, name, service_handler,
schema=SHELL_COMMAND_SCHEMA)
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

View File

@ -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',
side_effect=SubprocessError)
@patch('homeassistant.components.shell_command._LOGGER.error')