From 9521dad263932092b5393ef3becb94d2ce6f276d Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 12 Feb 2016 11:25:26 +0100 Subject: [PATCH 1/6] Added a command line notification platform that could be used for all kind of custom notifications --- .../components/notify/command_line.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 homeassistant/components/notify/command_line.py diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py new file mode 100644 index 00000000000..2beb78ccdca --- /dev/null +++ b/homeassistant/components/notify/command_line.py @@ -0,0 +1,46 @@ +""" +homeassistant.components.notify.command_line +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +command_line notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.command_line/ +""" +import logging +import subprocess +import shlex +from homeassistant.helpers import validate_config +from homeassistant.components.notify import ( + DOMAIN, BaseNotificationService) + +_LOGGER = logging.getLogger(__name__) + + +def get_service(hass, config): + """ Get the Command Line notification service. """ + + if not validate_config({DOMAIN: config}, + {DOMAIN: ['command']}, + _LOGGER): + return None + + command = config['command'] + + return CommandLineNotificationService(command) + + +# pylint: disable=too-few-public-methods +class CommandLineNotificationService(BaseNotificationService): + """ Implements notification service for the Command Line service. """ + + def __init__(self, command): + self.command = command + + def send_message(self, message="", **kwargs): + """ Send a message to a command_line. """ + try: + subprocess.call("{} \"{}\"".format(self.command, + shlex.quote(message)), + shell=True) + except subprocess.CalledProcessError: + _LOGGER.error('Command failed: %s', self.command) From 10a20f802bbc4c7cb2fb2de642f720304cb40a8f Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 12 Feb 2016 11:26:21 +0100 Subject: [PATCH 2/6] Updated coverage --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 8fefa8a44b8..96db38ea4e2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -103,6 +103,7 @@ omit = homeassistant/components/notify/free_mobile.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py + homeassistant/components/notify/command_line.py homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushetta.py homeassistant/components/notify/pushover.py From 5a03ddd7e0e9bf947c29d442cff3cb058a8cce31 Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 12 Feb 2016 19:31:28 +0100 Subject: [PATCH 3/6] Removed "" and changed the call to check_call to make it race the appropriate error --- homeassistant/components/notify/command_line.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index 2beb78ccdca..aa753e4f02f 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -39,8 +39,9 @@ class CommandLineNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """ Send a message to a command_line. """ try: - subprocess.call("{} \"{}\"".format(self.command, - shlex.quote(message)), - shell=True) + subprocess.check_call( + "{} {}".format(self.command, + shlex.quote(message)), + shell=True) except subprocess.CalledProcessError: _LOGGER.error('Command failed: %s', self.command) From fa8857dfc5375064b377bdda493aafdfbb20e3fd Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Sun, 14 Feb 2016 22:22:11 +0100 Subject: [PATCH 4/6] Changed process communication to use stdin for the message because of security concerns. --- homeassistant/components/notify/command_line.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/notify/command_line.py b/homeassistant/components/notify/command_line.py index aa753e4f02f..025046f1e7c 100644 --- a/homeassistant/components/notify/command_line.py +++ b/homeassistant/components/notify/command_line.py @@ -8,7 +8,6 @@ https://home-assistant.io/components/notify.command_line/ """ import logging import subprocess -import shlex from homeassistant.helpers import validate_config from homeassistant.components.notify import ( DOMAIN, BaseNotificationService) @@ -38,10 +37,12 @@ class CommandLineNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """ Send a message to a command_line. """ + try: - subprocess.check_call( - "{} {}".format(self.command, - shlex.quote(message)), - shell=True) - except subprocess.CalledProcessError: - _LOGGER.error('Command failed: %s', self.command) + proc = subprocess.Popen(self.command, universal_newlines=True, + stdin=subprocess.PIPE, shell=True) + proc.communicate(input=message) + if proc.returncode != 0: + _LOGGER.error('Command failed: %s', self.command) + except subprocess.SubprocessError: + _LOGGER.error('Error trying to exec Command: %s', self.command) From fa6d9adcba444c2860e25f3830d858c20b0a62cc Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 19 Feb 2016 22:30:38 +0100 Subject: [PATCH 5/6] Added some unittests for the command_line notification --- tests/components/notify/test_command_line.py | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/components/notify/test_command_line.py diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py new file mode 100644 index 00000000000..f920590a21c --- /dev/null +++ b/tests/components/notify/test_command_line.py @@ -0,0 +1,58 @@ +""" +tests.components.notify.test_command_line +~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests command line notification. +""" +import os +import tempfile +import unittest + +from homeassistant import core +import homeassistant.components.notify as notify +from unittest.mock import patch + + +class TestCommandLine(unittest.TestCase): + """ Test the command line. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = core.HomeAssistant() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_command_line_output(self): + with tempfile.TemporaryDirectory() as tempdirname: + filename = os.path.join(tempdirname, 'message.txt') + message = 'one, two, testing, testing' + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'command_line', + 'command': 'echo $(cat) > {}'.format(filename) + } + })) + + self.hass.services.call('notify', 'test', {'message': message}, + blocking=True) + + result = open(filename).read() + # the echo command adds a line break + self.assertEqual(result, "{}\n".format(message)) + + @patch('homeassistant.components.notify.command_line._LOGGER.error') + def test_error_for_none_zero_exit_code(self, mock_error): + """ Test if an error if logged for non zero exit codes. """ + self.assertTrue(notify.setup(self.hass, { + 'notify': { + 'name': 'test', + 'platform': 'command_line', + 'command': 'echo $(cat); exit 1' + } + })) + + self.hass.services.call('notify', 'test', {'message': 'error'}, + blocking=True) + self.assertEqual(1, mock_error.call_count) From c85875ddf429ed3c510e5111a02bf77d0d62390e Mon Sep 17 00:00:00 2001 From: Stefan Jonasson Date: Fri, 19 Feb 2016 22:34:04 +0100 Subject: [PATCH 6/6] removed command_line from coveragerc --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 96db38ea4e2..8fefa8a44b8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -103,7 +103,6 @@ omit = homeassistant/components/notify/free_mobile.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py - homeassistant/components/notify/command_line.py homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushetta.py homeassistant/components/notify/pushover.py