Make Certbot runnable on Windows (#6296)

* Add and use a compatibility layer to allow certbot to be run on windows.

* Fix path comparison

* Corrections on compat and util for tests

* Less intrusive way to parse prefix in webroot plugin working for both linux and windows.

* Disable pylint import-error for some optional imports in compat.py

* Ensure path is normalized before prefixes are generated in webroot plugin

* Same prefixes in linux and windows, in fact root path is not needed in webroot plugin

* Check that user has administrative rights before continuing on windows (necessary for symlink creation)

* More straightforward way to test administrative rights on windows

* Try to resolve import error in travis ci

* OK. We go for full introspection to trick the ci.

* Move the administrative rights control to the certbot entrypoint

* Add comment for a really non trivial code.

* Allow some commands to be run on a shell without admin rights

* Avoid races conditions on windows for lock files

* Add sphinx doc to the compat functions.

* Remove irrelevant Windows error in the lock mechanism.

* Some corrections on compat
This commit is contained in:
Adrien Ferrand
2018-09-08 07:34:27 -07:00
committed by Brad Warren
parent 5d1c6d28d5
commit 85a859d63f
19 changed files with 204 additions and 67 deletions
+3 -2
View File
@@ -17,6 +17,7 @@ import zope.component
from acme import fields as acme_fields from acme import fields as acme_fields
from acme import messages from acme import messages
from certbot import compat
from certbot import constants from certbot import constants
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -140,7 +141,7 @@ class AccountFileStorage(interfaces.AccountStorage):
""" """
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid(), util.make_or_verify_dir(config.accounts_dir, 0o700, compat.os_geteuid(),
self.config.strict_permissions) self.config.strict_permissions)
def _account_dir_path(self, account_id): def _account_dir_path(self, account_id):
@@ -323,7 +324,7 @@ class AccountFileStorage(interfaces.AccountStorage):
def _save(self, account, acme, regr_only): def _save(self, account, acme, regr_only):
account_dir_path = self._account_dir_path(account.id) account_dir_path = self._account_dir_path(account.id)
util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), util.make_or_verify_dir(account_dir_path, 0o700, compat.os_geteuid(),
self.config.strict_permissions) self.config.strict_permissions)
try: try:
with open(self._regr_path(account_dir_path), "w") as regr_file: with open(self._regr_path(account_dir_path), "w") as regr_file:
+3 -2
View File
@@ -8,6 +8,7 @@ import traceback
import zope.component import zope.component
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import compat
from certbot import crypto_util from certbot import crypto_util
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -104,7 +105,7 @@ def lineage_for_certname(cli_config, certname):
"""Find a lineage object with name certname.""" """Find a lineage object with name certname."""
configs_dir = cli_config.renewal_configs_dir configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there # Verify the directory is there
util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) util.make_or_verify_dir(configs_dir, mode=0o755, uid=compat.os_geteuid())
try: try:
renewal_file = storage.renewal_file_for_certname(cli_config, certname) renewal_file = storage.renewal_file_for_certname(cli_config, certname)
except errors.CertStorageError: except errors.CertStorageError:
@@ -374,7 +375,7 @@ def _search_lineages(cli_config, func, initial_rv, *args):
""" """
configs_dir = cli_config.renewal_configs_dir configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there # Verify the directory is there
util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) util.make_or_verify_dir(configs_dir, mode=0o755, uid=compat.os_geteuid())
rv = initial_rv rv = initial_rv
for renewal_file in storage.renewal_conf_files(cli_config): for renewal_file in storage.renewal_conf_files(cli_config):
+2 -1
View File
@@ -24,6 +24,7 @@ import certbot
from certbot import account from certbot import account
from certbot import auth_handler from certbot import auth_handler
from certbot import cli from certbot import cli
from certbot import compat
from certbot import constants from certbot import constants
from certbot import crypto_util from certbot import crypto_util
from certbot import eff from certbot import eff
@@ -447,7 +448,7 @@ class Client(object):
""" """
for path in cert_path, chain_path, fullchain_path: for path in cert_path, chain_path, fullchain_path:
util.make_or_verify_dir( util.make_or_verify_dir(
os.path.dirname(path), 0o755, os.geteuid(), os.path.dirname(path), 0o755, compat.os_geteuid(),
self.config.strict_permissions) self.config.strict_permissions)
+140
View File
@@ -0,0 +1,140 @@
"""
Compatibility layer to run certbot both on Linux and Windows.
The approach used here is similar to Modernizr for Web browsers.
We do not check the plateform type to determine if a particular logic is supported.
Instead, we apply a logic, and then fallback to another logic if first logic
is not supported at runtime.
Then logic chains are abstracted into single functions to be exposed to certbot.
"""
import os
import select
import sys
import errno
import ctypes
from certbot import errors
try:
# Linux specific
import fcntl # pylint: disable=import-error
except ImportError:
# Windows specific
import msvcrt # pylint: disable=import-error
UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [
'certificates', 'enhance', 'revoke', 'delete',
'register', 'unregister', 'config_changes', 'plugins']
def raise_for_non_administrative_windows_rights(subcommand):
"""
On Windows, raise if current shell does not have the administrative rights.
Do nothing on Linux.
:param str subcommand: The subcommand (like 'certonly') passed to the certbot client.
:raises .errors.Error: If the provided subcommand must be run on a shell with
administrative rights, and current shell does not have these rights.
"""
# Why not simply try ctypes.windll.shell32.IsUserAnAdmin() and catch AttributeError ?
# Because windll exists only on a Windows runtime, and static code analysis engines
# do not like at all non existent objects when run from Linux (even if we handle properly
# all the cases in the code).
# So we access windll only by reflection to trick theses engines.
if hasattr(ctypes, 'windll') and subcommand not in UNPRIVILEGED_SUBCOMMANDS_ALLOWED:
windll = getattr(ctypes, 'windll')
if windll.shell32.IsUserAnAdmin() == 0:
raise errors.Error(
'Error, "{0}" subcommand must be run on a shell with administrative rights.'
.format(subcommand))
def os_geteuid():
"""
Get current user uid
:returns: The current user uid.
:rtype: int
"""
try:
# Linux specific
return os.geteuid()
except AttributeError:
# Windows specific
return 0
def readline_with_timeout(timeout, prompt):
"""
Read user input to return the first line entered, or raise after specified timeout.
:param float timeout: The timeout in seconds given to the user.
:param str prompt: The prompt message to display to the user.
:returns: The first line entered by the user.
:rtype: str
"""
try:
# Linux specific
#
# Call to select can only be done like this on UNIX
rlist, _, _ = select.select([sys.stdin], [], [], timeout)
if not rlist:
raise errors.Error(
"Timed out waiting for answer to prompt '{0}'".format(prompt))
return rlist[0].readline()
except OSError:
# Windows specific
#
# No way with select to make a timeout to the user input on Windows,
# as select only supports socket in this case.
# So no timeout on Windows for now.
return sys.stdin.readline()
def lock_file(fd):
"""
Lock the file linked to the specified file descriptor.
:param int fd: The file descriptor of the file to lock.
"""
if 'fcntl' in sys.modules:
# Linux specific
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
else:
# Windows specific
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
def release_locked_file(fd, path):
"""
Remove, close, and release a lock file specified by its file descriptor and its path.
:param int fd: The file descriptor of the lock file.
:param str path: The path of the lock file.
"""
# Linux specific
#
# It is important the lock file is removed before it's released,
# otherwise:
#
# process A: open lock file
# process B: release lock file
# process A: lock file
# process A: check device and inode
# process B: delete file
# process C: open and lock a different file at the same path
try:
os.remove(path)
except OSError as err:
if err.errno == errno.EACCES:
# Windows specific
# We will not be able to remove a file before closing it.
# To avoid race conditions described for Linux, we will not delete the lockfile,
# just close it to be reused on the next Certbot call.
pass
else:
raise
finally:
os.close(fd)
+3 -2
View File
@@ -25,6 +25,7 @@ from OpenSSL import SSL # type: ignore
from acme import crypto_util as acme_crypto_util from acme import crypto_util as acme_crypto_util
from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module
from certbot import compat
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
from certbot import util from certbot import util
@@ -60,7 +61,7 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
config = zope.component.getUtility(interfaces.IConfig) config = zope.component.getUtility(interfaces.IConfig)
# Save file # Save file
util.make_or_verify_dir(key_dir, 0o700, os.geteuid(), util.make_or_verify_dir(key_dir, 0o700, compat.os_geteuid(),
config.strict_permissions) config.strict_permissions)
key_f, key_path = util.unique_file( key_f, key_path = util.unique_file(
os.path.join(key_dir, keyname), 0o600, "wb") os.path.join(key_dir, keyname), 0o600, "wb")
@@ -91,7 +92,7 @@ def init_save_csr(privkey, names, path):
privkey.pem, names, must_staple=config.must_staple) privkey.pem, names, must_staple=config.must_staple)
# Save CSR # Save CSR
util.make_or_verify_dir(path, 0o755, os.geteuid(), util.make_or_verify_dir(path, 0o755, compat.os_geteuid(),
config.strict_permissions) config.strict_permissions)
csr_f, csr_filename = util.unique_file( csr_f, csr_filename = util.unique_file(
os.path.join(path, "csr-certbot.pem"), 0o644, "wb") os.path.join(path, "csr-certbot.pem"), 0o644, "wb")
+2 -7
View File
@@ -1,12 +1,12 @@
"""Certbot display.""" """Certbot display."""
import logging import logging
import os import os
import select
import sys import sys
import textwrap import textwrap
import zope.interface import zope.interface
from certbot import compat
from certbot import constants from certbot import constants
from certbot import interfaces from certbot import interfaces
from certbot import errors from certbot import errors
@@ -79,13 +79,8 @@ def input_with_timeout(prompt=None, timeout=36000.0):
sys.stdout.write(prompt) sys.stdout.write(prompt)
sys.stdout.flush() sys.stdout.flush()
# select can only be used like this on UNIX line = compat.readline_with_timeout(timeout, prompt)
rlist, _, _ = select.select([sys.stdin], [], [], timeout)
if not rlist:
raise errors.Error(
"Timed out waiting for answer to prompt '{0}'".format(prompt))
line = rlist[0].readline()
if not line: if not line:
raise EOFError raise EOFError
return line.rstrip('\n') return line.rstrip('\n')
+3 -18
View File
@@ -1,9 +1,9 @@
"""Implements file locks for locking files and directories in UNIX.""" """Implements file locks for locking files and directories in UNIX."""
import errno import errno
import fcntl
import logging import logging
import os import os
from certbot import compat
from certbot import errors from certbot import errors
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -74,7 +74,7 @@ class LockFile(object):
""" """
try: try:
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) compat.lock_file(fd)
except IOError as err: except IOError as err:
if err.errno in (errno.EACCES, errno.EAGAIN): if err.errno in (errno.EACCES, errno.EAGAIN):
logger.debug( logger.debug(
@@ -118,22 +118,7 @@ class LockFile(object):
def release(self): def release(self):
"""Remove, close, and release the lock file.""" """Remove, close, and release the lock file."""
# It is important the lock file is removed before it's released,
# otherwise:
#
# process A: open lock file
# process B: release lock file
# process A: lock file
# process A: check device and inode
# process B: delete file
# process C: open and lock a different file at the same path
#
# Calling os.remove on a file that's in use doesn't work on
# Windows, but neither does locking with fcntl.
try: try:
os.remove(self._path) compat.release_locked_file(self._fd, self._path)
finally:
try:
os.close(self._fd)
finally: finally:
self._fd = None self._fd = None
+2 -1
View File
@@ -23,6 +23,7 @@ import traceback
from acme import messages from acme import messages
from certbot import compat
from certbot import constants from certbot import constants
from certbot import errors from certbot import errors
from certbot import util from certbot import util
@@ -133,7 +134,7 @@ def setup_log_file_handler(config, logfile, fmt):
# TODO: logs might contain sensitive data such as contents of the # TODO: logs might contain sensitive data such as contents of the
# private key! #525 # private key! #525
util.set_up_core_dir( util.set_up_core_dir(
config.logs_dir, 0o700, os.geteuid(), config.strict_permissions) config.logs_dir, 0o700, compat.os_geteuid(), config.strict_permissions)
log_file_path = os.path.join(config.logs_dir, logfile) log_file_path = os.path.join(config.logs_dir, logfile)
try: try:
handler = logging.handlers.RotatingFileHandler( handler = logging.handlers.RotatingFileHandler(
+9 -3
View File
@@ -19,6 +19,7 @@ from certbot import account
from certbot import cert_manager from certbot import cert_manager
from certbot import cli from certbot import cli
from certbot import client from certbot import client
from certbot import compat
from certbot import configuration from certbot import configuration
from certbot import constants from certbot import constants
from certbot import crypto_util from certbot import crypto_util
@@ -1289,16 +1290,16 @@ def make_or_verify_needed_dirs(config):
""" """
util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE,
os.geteuid(), config.strict_permissions) compat.os_geteuid(), config.strict_permissions)
util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE,
os.geteuid(), config.strict_permissions) compat.os_geteuid(), config.strict_permissions)
hook_dirs = (config.renewal_pre_hooks_dir, hook_dirs = (config.renewal_pre_hooks_dir,
config.renewal_deploy_hooks_dir, config.renewal_deploy_hooks_dir,
config.renewal_post_hooks_dir,) config.renewal_post_hooks_dir,)
for hook_dir in hook_dirs: for hook_dir in hook_dirs:
util.make_or_verify_dir(hook_dir, util.make_or_verify_dir(hook_dir,
uid=os.geteuid(), uid=compat.os_geteuid(),
strict=config.strict_permissions) strict=config.strict_permissions)
@@ -1333,6 +1334,7 @@ def main(cli_args=sys.argv[1:]):
:raises errors.Error: error if plugin command is not supported :raises errors.Error: error if plugin command is not supported
""" """
log.pre_arg_parse_setup() log.pre_arg_parse_setup()
plugins = plugins_disco.PluginsRegistry.find_all() plugins = plugins_disco.PluginsRegistry.find_all()
@@ -1346,6 +1348,10 @@ def main(cli_args=sys.argv[1:]):
config = configuration.NamespaceConfig(args) config = configuration.NamespaceConfig(args)
zope.component.provideUtility(config) zope.component.provideUtility(config)
# On windows, shell without administrative right cannot create symlinks required by certbot.
# So we check the rights before continuing.
compat.raise_for_non_administrative_windows_rights(config.verb)
try: try:
log.post_arg_parse_setup(config) log.post_arg_parse_setup(config)
make_or_verify_needed_dirs(config) make_or_verify_needed_dirs(config)
+4 -3
View File
@@ -9,18 +9,19 @@ logger = logging.getLogger(__name__)
def get_prefixes(path): def get_prefixes(path):
"""Retrieves all possible path prefixes of a path, in descending order """Retrieves all possible path prefixes of a path, in descending order
of length. For instance, of length. For instance,
/a/b/c/ => ['/a/b/c/', '/a/b/c', '/a/b', '/a', '/'] (linux) /a/b/c returns ['/a/b/c', '/a/b', '/a', '/']
(windows) C:\\a\\b\\c returns ['C:\\a\\b\\c', 'C:\\a\\b', 'C:\\a', 'C:']
:param str path: the path to break into prefixes :param str path: the path to break into prefixes
:returns: all possible path prefixes of given path in descending order :returns: all possible path prefixes of given path in descending order
:rtype: `list` of `str` :rtype: `list` of `str`
""" """
prefix = path prefix = os.path.normpath(path)
prefixes = [] prefixes = []
while len(prefix) > 0: while len(prefix) > 0:
prefixes.append(prefix) prefixes.append(prefix)
prefix, _ = os.path.split(prefix) prefix, _ = os.path.split(prefix)
# break once we hit '/' # break once we hit the root path
if prefix == prefixes[-1]: if prefix == prefixes[-1]:
break break
return prefixes return prefixes
+3 -3
View File
@@ -9,9 +9,9 @@ class GetPrefixTest(unittest.TestCase):
"""Tests for certbot.plugins.get_prefixes.""" """Tests for certbot.plugins.get_prefixes."""
def test_get_prefix(self): def test_get_prefix(self):
from certbot.plugins.util import get_prefixes from certbot.plugins.util import get_prefixes
self.assertEqual(get_prefixes("/a/b/c/"), ['/a/b/c/', '/a/b/c', '/a/b', '/a', '/']) self.assertEqual(get_prefixes('/a/b/c'), ['/a/b/c', '/a/b', '/a', '/'])
self.assertEqual(get_prefixes("/"), ["/"]) self.assertEqual(get_prefixes('/'), ['/'])
self.assertEqual(get_prefixes("a"), ["a"]) self.assertEqual(get_prefixes('a'), ['a'])
class PathSurgeryTest(unittest.TestCase): class PathSurgeryTest(unittest.TestCase):
"""Tests for certbot.plugins.path_surgery.""" """Tests for certbot.plugins.path_surgery."""
+4 -2
View File
@@ -170,7 +170,9 @@ to serve all files under specified web root ({0})."""
old_umask = os.umask(0o022) old_umask = os.umask(0o022)
try: try:
stat_path = os.stat(path) stat_path = os.stat(path)
for prefix in sorted(util.get_prefixes(self.full_roots[name]), key=len): # We ignore the last prefix in the next iteration,
# as it does not correspond to a folder path ('/' or 'C:')
for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len):
try: try:
# This is coupled with the "umask" call above because # This is coupled with the "umask" call above because
# os.mkdir's "mode" parameter may not always work: # os.mkdir's "mode" parameter may not always work:
@@ -180,7 +182,7 @@ to serve all files under specified web root ({0})."""
# Set owner as parent directory if possible # Set owner as parent directory if possible
try: try:
os.chown(prefix, stat_path.st_uid, stat_path.st_gid) os.chown(prefix, stat_path.st_uid, stat_path.st_gid)
except OSError as exception: except (OSError, AttributeError) as exception:
logger.info("Unable to change owner and uid of webroot directory") logger.info("Unable to change owner and uid of webroot directory")
logger.debug("Error was: %s", exception) logger.debug("Error was: %s", exception)
except OSError as exception: except OSError as exception:
+4 -3
View File
@@ -10,6 +10,7 @@ import traceback
import six import six
import zope.component import zope.component
from certbot import compat
from certbot import constants from certbot import constants
from certbot import errors from certbot import errors
from certbot import interfaces from certbot import interfaces
@@ -65,7 +66,7 @@ class Reverter(object):
self.config = config self.config = config
util.make_or_verify_dir( util.make_or_verify_dir(
config.backup_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), config.backup_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(),
self.config.strict_permissions) self.config.strict_permissions)
def revert_temporary_config(self): def revert_temporary_config(self):
@@ -219,7 +220,7 @@ class Reverter(object):
""" """
util.make_or_verify_dir( util.make_or_verify_dir(
cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), cp_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(),
self.config.strict_permissions) self.config.strict_permissions)
op_fd, existing_filepaths = self._read_and_append( op_fd, existing_filepaths = self._read_and_append(
@@ -433,7 +434,7 @@ class Reverter(object):
cp_dir = self.config.in_progress_dir cp_dir = self.config.in_progress_dir
util.make_or_verify_dir( util.make_or_verify_dir(
cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), cp_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(),
self.config.strict_permissions) self.config.strict_permissions)
return cp_dir return cp_dir
+12 -7
View File
@@ -48,18 +48,23 @@ class NamespaceConfigTest(test_util.ConfigTestCase):
mock_constants.TEMP_CHECKPOINT_DIR = 't' mock_constants.TEMP_CHECKPOINT_DIR = 't'
self.assertEqual( self.assertEqual(
self.config.accounts_dir, os.path.join( os.path.normpath(self.config.accounts_dir),
self.config.config_dir, 'acc/acme-server.org:443/new')) os.path.normpath(os.path.join(self.config.config_dir, 'acc/acme-server.org:443/new')))
self.assertEqual( self.assertEqual(
self.config.backup_dir, os.path.join(self.config.work_dir, 'backups')) os.path.normpath(self.config.backup_dir),
os.path.normpath(os.path.join(self.config.work_dir, 'backups')))
self.assertEqual( self.assertEqual(
self.config.csr_dir, os.path.join(self.config.config_dir, 'csr')) os.path.normpath(self.config.csr_dir),
os.path.normpath(os.path.join(self.config.config_dir, 'csr')))
self.assertEqual( self.assertEqual(
self.config.in_progress_dir, os.path.join(self.config.work_dir, '../p')) os.path.normpath(self.config.in_progress_dir),
os.path.normpath(os.path.join(self.config.work_dir, '../p')))
self.assertEqual( self.assertEqual(
self.config.key_dir, os.path.join(self.config.config_dir, 'keys')) os.path.normpath(self.config.key_dir),
os.path.normpath(os.path.join(self.config.config_dir, 'keys')))
self.assertEqual( self.assertEqual(
self.config.temp_checkpoint_dir, os.path.join(self.config.work_dir, 't')) os.path.normpath(self.config.temp_checkpoint_dir),
os.path.normpath(os.path.join(self.config.work_dir, 't')))
def test_absolute_paths(self): def test_absolute_paths(self):
from certbot.configuration import NamespaceConfig from certbot.configuration import NamespaceConfig
+2 -6
View File
@@ -34,7 +34,7 @@ class InputWithTimeoutTest(unittest.TestCase):
def test_input(self, prompt=None): def test_input(self, prompt=None):
expected = "foo bar" expected = "foo bar"
stdin = six.StringIO(expected + "\n") stdin = six.StringIO(expected + "\n")
with mock.patch("certbot.display.util.select.select") as mock_select: with mock.patch("certbot.compat.select.select") as mock_select:
mock_select.return_value = ([stdin], [], [],) mock_select.return_value = ([stdin], [], [],)
self.assertEqual(self._call(prompt), expected) self.assertEqual(self._call(prompt), expected)
@@ -321,11 +321,7 @@ class FileOutputDisplayTest(unittest.TestCase):
class NoninteractiveDisplayTest(unittest.TestCase): class NoninteractiveDisplayTest(unittest.TestCase):
"""Test non-interactive display. """Test non-interactive display. These tests are pretty easy!"""
These tests are pretty easy!
"""
def setUp(self): def setUp(self):
super(NoninteractiveDisplayTest, self).setUp() super(NoninteractiveDisplayTest, self).setUp()
self.mock_stdout = mock.MagicMock() self.mock_stdout = mock.MagicMock()
+1 -1
View File
@@ -89,7 +89,7 @@ class LockFileTest(test_util.TempDirTestCase):
lock_file.release() lock_file.release()
self.assertFalse(os.path.exists(self.lock_path)) self.assertFalse(os.path.exists(self.lock_path))
@mock.patch('certbot.lock.fcntl.lockf') @mock.patch('certbot.compat.fcntl.lockf')
def test_unexpected_lockf_err(self, mock_lockf): def test_unexpected_lockf_err(self, mock_lockf):
msg = 'hi there' msg = 'hi there'
mock_lockf.side_effect = IOError(msg) mock_lockf.side_effect = IOError(msg)
+3 -2
View File
@@ -19,6 +19,7 @@ from six.moves import reload_module # pylint: disable=import-error
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import account from certbot import account
from certbot import cli from certbot import cli
from certbot import compat
from certbot import constants from certbot import constants
from certbot import configuration from certbot import configuration
from certbot import crypto_util from certbot import crypto_util
@@ -1577,7 +1578,7 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase):
for core_dir in (self.config.config_dir, self.config.work_dir,): for core_dir in (self.config.config_dir, self.config.work_dir,):
mock_util.set_up_core_dir.assert_any_call( mock_util.set_up_core_dir.assert_any_call(
core_dir, constants.CONFIG_DIRS_MODE, core_dir, constants.CONFIG_DIRS_MODE,
os.geteuid(), self.config.strict_permissions compat.os_geteuid(), self.config.strict_permissions
) )
hook_dirs = (self.config.renewal_pre_hooks_dir, hook_dirs = (self.config.renewal_pre_hooks_dir,
@@ -1586,7 +1587,7 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase):
for hook_dir in hook_dirs: for hook_dir in hook_dirs:
# default mode of 755 is used # default mode of 755 is used
mock_util.make_or_verify_dir.assert_any_call( mock_util.make_or_verify_dir.assert_any_call(
hook_dir, uid=os.geteuid(), hook_dir, uid=compat.os_geteuid(),
strict=self.config.strict_permissions) strict=self.config.strict_permissions)
-1
View File
@@ -362,7 +362,6 @@ def lock_and_call(func, lock_path):
child.join() child.join()
assert child.exitcode == 0 assert child.exitcode == 0
def hold_lock(cv, lock_path): # pragma: no cover def hold_lock(cv, lock_path): # pragma: no cover
"""Acquire a file lock at lock_path and wait to release it. """Acquire a file lock at lock_path and wait to release it.
+3 -2
View File
@@ -10,6 +10,7 @@ import mock
import six import six
from six.moves import reload_module # pylint: disable=import-error from six.moves import reload_module # pylint: disable=import-error
from certbot import compat
from certbot import errors from certbot import errors
import certbot.tests.util as test_util import certbot.tests.util as test_util
@@ -116,7 +117,7 @@ class SetUpCoreDirTest(test_util.TempDirTestCase):
@mock.patch('certbot.util.lock_dir_until_exit') @mock.patch('certbot.util.lock_dir_until_exit')
def test_success(self, mock_lock): def test_success(self, mock_lock):
new_dir = os.path.join(self.tempdir, 'new') new_dir = os.path.join(self.tempdir, 'new')
self._call(new_dir, 0o700, os.geteuid(), False) self._call(new_dir, 0o700, compat.os_geteuid(), False)
self.assertTrue(os.path.exists(new_dir)) self.assertTrue(os.path.exists(new_dir))
self.assertEqual(mock_lock.call_count, 1) self.assertEqual(mock_lock.call_count, 1)
@@ -124,7 +125,7 @@ class SetUpCoreDirTest(test_util.TempDirTestCase):
def test_failure(self, mock_make_or_verify): def test_failure(self, mock_make_or_verify):
mock_make_or_verify.side_effect = OSError mock_make_or_verify.side_effect = OSError
self.assertRaises(errors.Error, self._call, self.assertRaises(errors.Error, self._call,
self.tempdir, 0o700, os.geteuid(), False) self.tempdir, 0o700, compat.os_geteuid(), False)
class MakeOrVerifyDirTest(test_util.TempDirTestCase): class MakeOrVerifyDirTest(test_util.TempDirTestCase):