Compare commits

...

6 Commits

Author SHA1 Message Date
Brad Warren
6192218df5 Revert encoding changes 2020-04-20 11:51:40 -07:00
Brad Warren
c1038dc139 revert hook.py changes 2020-04-20 11:45:02 -07:00
Brad Warren
91c63e7529 Merge branch 'master' into azure-test-hook-powershell-window 2020-04-20 11:33:10 -07:00
Brad Warren
2b78d3c4e2 try still using .bat 2020-04-20 11:31:43 -07:00
Adrien Ferrand
0b5df9049a Fix hook test 2020-02-24 20:14:15 +01:00
Adrien Ferrand
330977e988 Run hooks in powershell on Windows 2020-02-24 18:00:01 +01:00
5 changed files with 91 additions and 70 deletions

View File

@@ -2,14 +2,13 @@
from __future__ import print_function from __future__ import print_function
import logging import logging
from subprocess import PIPE
from subprocess import Popen
from acme.magic_typing import List from acme.magic_typing import List
from acme.magic_typing import Set from acme.magic_typing import Set
from certbot import errors from certbot import errors
from certbot import util from certbot import util
from certbot.compat import filesystem from certbot.compat import filesystem
from certbot.compat import misc
from certbot.compat import os from certbot.compat import os
from certbot.plugins import util as plug_util from certbot.plugins import util as plug_util
@@ -229,36 +228,10 @@ def _run_hook(cmd_name, shell_cmd):
:type shell_cmd: `list` of `str` or `str` :type shell_cmd: `list` of `str` or `str`
:returns: stderr if there was any""" :returns: stderr if there was any"""
err, _ = execute(cmd_name, shell_cmd) err, _ = misc.execute_command(cmd_name, shell_cmd)
return err return err
def execute(cmd_name, shell_cmd):
"""Run a command.
:param str cmd_name: the user facing name of the hook being run
:param shell_cmd: shell command to execute
:type shell_cmd: `list` of `str` or `str`
:returns: `tuple` (`str` stderr, `str` stdout)"""
logger.info("Running %s command: %s", cmd_name, shell_cmd)
# universal_newlines causes Popen.communicate()
# to return str objects instead of bytes in Python 3
cmd = Popen(shell_cmd, shell=True, stdout=PIPE,
stderr=PIPE, universal_newlines=True)
out, err = cmd.communicate()
base_cmd = os.path.basename(shell_cmd.split(None, 1)[0])
if out:
logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out)
if cmd.returncode != 0:
logger.error('%s command "%s" returned error code %d',
cmd_name, shell_cmd, cmd.returncode)
if err:
logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err)
return err, out
def list_hooks(dir_path): def list_hooks(dir_path):
"""List paths to all hooks found in dir_path in sorted order. """List paths to all hooks found in dir_path in sorted order.

View File

@@ -9,6 +9,7 @@ from certbot import errors
from certbot import interfaces from certbot import interfaces
from certbot import reverter from certbot import reverter
from certbot._internal import hooks from certbot._internal import hooks
from certbot.compat import misc
from certbot.compat import os from certbot.compat import os
from certbot.plugins import common from certbot.plugins import common
@@ -186,4 +187,4 @@ permitted by DNS standards.)
self.reverter.recovery_routine() self.reverter.recovery_routine()
def _execute_hook(self, hook_name): def _execute_hook(self, hook_name):
return hooks.execute(self.option_name(hook_name), self.conf(hook_name)) return misc.execute_command(self.option_name(hook_name), self.conf(hook_name))

View File

@@ -4,7 +4,9 @@ particular category.
""" """
from __future__ import absolute_import from __future__ import absolute_import
import logging
import select import select
import subprocess
import sys import sys
from certbot import errors from certbot import errors
@@ -17,6 +19,7 @@ except ImportError: # pragma: no cover
POSIX_MODE = True POSIX_MODE = True
logger = logging.getLogger(__name__)
# For Linux: define OS specific standard binary directories # For Linux: define OS specific standard binary directories
STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else [] STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else []
@@ -109,3 +112,39 @@ def underscores_for_unsupported_characters_in_path(path):
# Windows specific # Windows specific
drive, tail = os.path.splitdrive(path) drive, tail = os.path.splitdrive(path)
return drive + tail.replace(':', '_') return drive + tail.replace(':', '_')
def execute_command(cmd_name, shell_cmd):
"""
Run a command:
- on Linux command will be run by the standard shell selected with Popen(shell=True)
- on Windows command will be run in a Powershell shell
:param str cmd_name: the user facing name of the hook being run
:param str shell_cmd: shell command to execute
:type shell_cmd: `list` of `str` or `str`
:returns: `tuple` (`str` stderr, `str` stdout)
"""
logger.info("Running %s command: %s", cmd_name, shell_cmd)
if POSIX_MODE:
cmd = subprocess.Popen(shell_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=True)
else:
line = ['powershell.exe', '-Command', shell_cmd]
cmd = subprocess.Popen(line, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
# universal_newlines causes Popen.communicate()
# to return str objects instead of bytes in Python 3
out, err = cmd.communicate()
base_cmd = os.path.basename(shell_cmd.split(None, 1)[0])
if out:
logger.info('Output from %s command %s:\n%s', cmd_name, base_cmd, out)
if cmd.returncode != 0:
logger.error('%s command "%s" returned error code %d',
cmd_name, shell_cmd, cmd.returncode)
if err:
logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err)
return err, out

View File

@@ -0,0 +1,44 @@
"""Tests for certbot.compat.misc"""
import mock
import unittest
from certbot.compat import os
class ExecuteTest(unittest.TestCase):
"""Tests for certbot.compat.misc.execute_command."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.compat.misc import execute_command
return execute_command(*args, **kwargs)
def test_it(self):
for returncode in range(0, 2):
for stdout in ("", "Hello World!",):
for stderr in ("", "Goodbye Cruel World!"):
self._test_common(returncode, stdout, stderr)
def _test_common(self, returncode, stdout, stderr):
given_command = "foo"
given_name = "foo-hook"
with mock.patch("certbot.compat.misc.subprocess.Popen") as mock_popen:
mock_popen.return_value.communicate.return_value = (stdout, stderr)
mock_popen.return_value.returncode = returncode
with mock.patch("certbot.compat.misc.logger") as mock_logger:
self.assertEqual(self._call(given_name, given_command), (stderr, stdout))
executed_command = mock_popen.call_args[1].get(
"args", mock_popen.call_args[0][0])
if os.name == 'nt':
expected_command = ['powershell.exe', '-Command', given_command]
else:
expected_command = given_command
self.assertEqual(executed_command, expected_command)
mock_logger.info.assert_any_call("Running %s command: %s",
given_name, given_command)
if stdout:
mock_logger.info.assert_any_call(mock.ANY, mock.ANY,
mock.ANY, stdout)
if stderr or returncode:
self.assertTrue(mock_logger.error.called)

View File

@@ -72,13 +72,13 @@ class HookTest(test_util.ConfigTestCase):
@classmethod @classmethod
def _call_with_mock_execute(cls, *args, **kwargs): def _call_with_mock_execute(cls, *args, **kwargs):
"""Calls self._call after mocking out certbot._internal.hooks.execute. """Calls self._call after mocking out certbot.compat.misc.execute_command.
The mock execute object is returned rather than the return value The mock execute object is returned rather than the return value
of self._call. of self._call.
""" """
with mock.patch("certbot._internal.hooks.execute") as mock_execute: with mock.patch("certbot.compat.misc.execute_command") as mock_execute:
mock_execute.return_value = ("", "") mock_execute.return_value = ("", "")
cls._call(*args, **kwargs) cls._call(*args, **kwargs)
return mock_execute return mock_execute
@@ -292,7 +292,7 @@ class RenewalHookTest(HookTest):
# pylint: disable=abstract-method # pylint: disable=abstract-method
def _call_with_mock_execute(self, *args, **kwargs): def _call_with_mock_execute(self, *args, **kwargs):
"""Calls self._call after mocking out certbot._internal.hooks.execute. """Calls self._call after mocking out certbot.compat.misc.execute_command.
The mock execute object is returned rather than the return value The mock execute object is returned rather than the return value
of self._call. The mock execute object asserts that environment of self._call. The mock execute object asserts that environment
@@ -313,7 +313,7 @@ class RenewalHookTest(HookTest):
self.assertEqual(os.environ["RENEWED_LINEAGE"], lineage) self.assertEqual(os.environ["RENEWED_LINEAGE"], lineage)
return ("", "") return ("", "")
with mock.patch("certbot._internal.hooks.execute") as mock_execute: with mock.patch("certbot.compat.misc.execute_command") as mock_execute:
mock_execute.side_effect = execute_side_effect mock_execute.side_effect = execute_side_effect
self._call(*args, **kwargs) self._call(*args, **kwargs)
return mock_execute return mock_execute
@@ -418,42 +418,6 @@ class RenewHookTest(RenewalHookTest):
mock_execute.assert_called_with("deploy-hook", self.config.renew_hook) mock_execute.assert_called_with("deploy-hook", self.config.renew_hook)
class ExecuteTest(unittest.TestCase):
"""Tests for certbot._internal.hooks.execute."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot._internal.hooks import execute
return execute(*args, **kwargs)
def test_it(self):
for returncode in range(0, 2):
for stdout in ("", "Hello World!",):
for stderr in ("", "Goodbye Cruel World!"):
self._test_common(returncode, stdout, stderr)
def _test_common(self, returncode, stdout, stderr):
given_command = "foo"
given_name = "foo-hook"
with mock.patch("certbot._internal.hooks.Popen") as mock_popen:
mock_popen.return_value.communicate.return_value = (stdout, stderr)
mock_popen.return_value.returncode = returncode
with mock.patch("certbot._internal.hooks.logger") as mock_logger:
self.assertEqual(self._call(given_name, given_command), (stderr, stdout))
executed_command = mock_popen.call_args[1].get(
"args", mock_popen.call_args[0][0])
self.assertEqual(executed_command, given_command)
mock_logger.info.assert_any_call("Running %s command: %s",
given_name, given_command)
if stdout:
mock_logger.info.assert_any_call(mock.ANY, mock.ANY,
mock.ANY, stdout)
if stderr or returncode:
self.assertTrue(mock_logger.error.called)
class ListHooksTest(test_util.TempDirTestCase): class ListHooksTest(test_util.TempDirTestCase):
"""Tests for certbot._internal.hooks.list_hooks.""" """Tests for certbot._internal.hooks.list_hooks."""