8000 New helper for templating args in command_line by gjohansson-ST · Pull Request #145899 · home-assistant/core · GitHub
[go: up one dir, main page]

Skip to content

New helper for templating args in command_line #145899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
New helper for templating args in command_line
  • Loading branch information
gjohansson-ST committed May 30, 2025
commit d004d6b56d26c77e1a6210e723ef680a5758afae
27 changes: 4 additions & 23 deletions homeassistant/components/command_line/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@
from homeassistant.components.notify import BaseNotificationService
from homeassistant.const import CONF_COMMAND
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.process import kill_subprocess

from .const import CONF_COMMAND_TIMEOUT, LOGGER
from .utils import render_template_args

_LOGGER = logging.getLogger(__name__)
8000
Expand Down Expand Up @@ -45,28 +44,10 @@ def __init__(self, command: str, timeout: int) -> None:

def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to a command line."""
command = self.command
if " " not in command:
prog = command
args = None
args_compiled = None
else:
prog, args = command.split(" ", 1)
args_compiled = Template(args, self.hass)
if not (command := render_template_args(self.hass, self.command)):
return

rendered_args = None
if args_compiled:
args_to_render = {"arguments": args}
try:
rendered_args = args_compiled.async_render(args_to_render)
except TemplateError as ex:
LOGGER.exception("Error rendering command template: %s", ex)
return

if rendered_args != args:
command = f"{prog} {rendered_args}"

LOGGER.debug("Running command: %s, with message: %s", command, message)
LOGGER.debug("Running with message: %s", message)

with subprocess.Popen( # noqa: S602 # shell by design
command,
Expand Down
47 changes: 39 additions & 8 deletions homeassistant/components/command_line/utils.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
from __future__ import annotations

import asyncio
import logging

_LOGGER = logging.getLogger(__name__)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.template import Template

from .const import LOGGER

_EXEC_FAILED_CODE = 127


Expand All @@ -18,22 +22,22 @@ async def async_call_shell_with_timeout(
return code is returned.
"""
try:
_LOGGER.debug("Running command: %s", command)
LOGGER.debug("Running command: %s", command)
proc = await asyncio.create_subprocess_shell( # shell by design
command,
close_fds=False, # required for posix_spawn
)
async with asyncio.timeout(timeout):
await proc.communicate()
except TimeoutError:
_LOGGER.error("Timeout for command: %s", command)
LOGGER.error("Timeout for command: %s", command)
return -1

return_code = proc.returncode
if return_code == _EXEC_FAILED_CODE:
_LOGGER.error("Error trying to exec command: %s", command)
LOGGER.error("Error trying to exec command: %s", command)
elif log_return_code and return_code != 0:
_LOGGER.error(
LOGGER.error(
"Command failed (with return code %s): %s",
proc.returncode,
command,
Expand All @@ -53,12 +57,39 @@ async def async_check_output_or_log(command: str, timeout: int) -> str | None:
stdout, _ = await proc.communicate()

if proc.returncode != 0:
_LOGGER.error(
LOGGER.error(
"Command failed (with return code %s): %s", proc.returncode, command
)
else:
return stdout.strip().decode("utf-8")
except TimeoutError:
_LOGGER.error("Timeout for command: %s", command)
LOGGER.error("Timeout for command: %s", command)

return None


def render_template_args(hass: HomeAssistant, command: str) -> str | None:
"""Render template arguments for command line utilities."""
if " " not in command:
prog = command
args = None
args_compiled = None
else:
prog, args = command.split(" ", 1)
args_compiled = Template(args, hass)

rendered_args = None
if args_compiled:
args_to_render = {"arguments": args}
try:
rendered_args = args_compiled.async_render(args_to_render)
except TemplateError as ex:
LOGGER.exception("Error rendering command template: %s", ex)
return None

if rendered_args != args:
command = f"{prog} {rendered_args}"

LOGGER.debug("Running command: %s", command)

return command
3 changes: 2 additions & 1 deletion tests/components/command_line/test_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ async def test_command_line_output_single_command(
await hass.services.async_call(
NOTIFY_DOMAIN, "test3", {"message": "test message"}, blocking=True
)
assert "Running command: echo, with message: test message" in caplog.text
assert "Running command: echo" in caplog.text
assert "Running with message: test message" in caplog.text


async def test_command_template(hass: HomeAssistant) -> None:
Expand Down
Loading
0