104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
"""
|
|
Exposes regular shell commands as services.
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
https://home-assistant.io/components/shell_command/
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
import shlex
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.exceptions import TemplateError
|
|
from homeassistant.core import ServiceCall
|
|
from homeassistant.helpers import config_validation as cv, template
|
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
|
|
|
|
|
DOMAIN = 'shell_command'
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: vol.Schema({
|
|
cv.slug: cv.string,
|
|
}),
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
@asyncio.coroutine
|
|
def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
|
"""Set up the shell_command component."""
|
|
conf = config.get(DOMAIN, {})
|
|
|
|
cache = {}
|
|
|
|
@asyncio.coroutine
|
|
def async_service_handler(service: ServiceCall) -> None:
|
|
"""Execute a shell command service."""
|
|
cmd = conf[service.service]
|
|
|
|
if cmd in cache:
|
|
prog, args, args_compiled = cache[cmd]
|
|
elif ' ' not in cmd:
|
|
prog = cmd
|
|
args = None
|
|
args_compiled = None
|
|
cache[cmd] = prog, args, args_compiled
|
|
else:
|
|
prog, args = cmd.split(' ', 1)
|
|
args_compiled = template.Template(args, hass)
|
|
cache[cmd] = prog, args, args_compiled
|
|
|
|
if args_compiled:
|
|
try:
|
|
rendered_args = args_compiled.async_render(service.data)
|
|
except TemplateError as ex:
|
|
_LOGGER.exception("Error rendering command template: %s", ex)
|
|
return
|
|
else:
|
|
rendered_args = None
|
|
|
|
if rendered_args == args:
|
|
# No template used. default behavior
|
|
|
|
# pylint: disable=no-member
|
|
create_process = asyncio.subprocess.create_subprocess_shell(
|
|
cmd,
|
|
loop=hass.loop,
|
|
stdin=None,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
else:
|
|
# Template used. Break into list and use create_subprocess_exec
|
|
# (which uses shell=False) for security
|
|
shlexed_cmd = [prog] + shlex.split(rendered_args)
|
|
|
|
# pylint: disable=no-member
|
|
create_process = asyncio.subprocess.create_subprocess_exec(
|
|
*shlexed_cmd,
|
|
loop=hass.loop,
|
|
stdin=None,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
|
|
process = yield from create_process
|
|
stdout_data, stderr_data = yield from process.communicate()
|
|
|
|
if stdout_data:
|
|
_LOGGER.debug("Stdout of command: `%s`, return code: %s:\n%s",
|
|
cmd, process.returncode, stdout_data)
|
|
if stderr_data:
|
|
_LOGGER.debug("Stderr of command: `%s`, return code: %s:\n%s",
|
|
cmd, process.returncode, stderr_data)
|
|
if process.returncode != 0:
|
|
_LOGGER.exception("Error running command: `%s`, return code: %s",
|
|
cmd, process.returncode)
|
|
|
|
for name in conf.keys():
|
|
hass.services.async_register(DOMAIN, name, async_service_handler)
|
|
return True
|