mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 13:59:02 -04:00
commit
4b7042f6aa
45 changed files with 462 additions and 661 deletions
31
.github/workflows/merged.yaml
vendored
Normal file
31
.github/workflows/merged.yaml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: Merge Event
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
if_merged:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create Mattermost Message
|
||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#example-of-a-script-injection-attack
|
||||
env:
|
||||
NUMBER: ${{ github.event.number }}
|
||||
PR_URL: https://github.com/${{ github.repository }}/pull/${{ github.event.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
USER: ${{ github.actor }}
|
||||
TITLE: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
jq --null-input \
|
||||
--arg number "$NUMBER" \
|
||||
--arg pr_url "$PR_URL" \
|
||||
--arg repo "$REPO" \
|
||||
--arg user "$USER" \
|
||||
--arg title "$TITLE" \
|
||||
'{ "text": "[\($repo)] | [\($title) #\($number)](\($pr_url)) was merged into master by \($user)" }' > mattermost.json
|
||||
- uses: mattermost/action-mattermost-notify@master
|
||||
env:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_MERGE_WEBHOOK }}
|
||||
25
.github/workflows/notify_weekly.yaml
vendored
Normal file
25
.github/workflows/notify_weekly.yaml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: Weekly Github Update
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every week on Thursday @ 13:00
|
||||
- cron: "0 13 * * 4"
|
||||
jobs:
|
||||
send-mattermost-message:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Create Mattermost Message
|
||||
run: |
|
||||
DATE=$(date --date="7 days ago" +"%Y-%m-%d")
|
||||
MERGED_URL="https://github.com/pulls?q=merged%3A%3E${DATE}+org%3Acertbot"
|
||||
UPDATED_URL="https://github.com/pulls?q=updated%3A%3E${DATE}+org%3Acertbot"
|
||||
echo "{\"text\":\"## Updates Across Certbot Repos\n\n
|
||||
- Certbot team members SHOULD look at: [link]($MERGED_URL)\n\n
|
||||
- Certbot team members MAY also want to look at: [link]($UPDATED_URL)\n\n
|
||||
- Want to Discuss something today? Place it [here](https://docs.google.com/document/d/17YMUbtC1yg6MfiTMwT8zVm9LmO-cuGVBom0qFn8XJBM/edit?usp=sharing) and we can meet today on Zoom.\n\n
|
||||
- The key words SHOULD and MAY in this message are to be interpreted as described in [RFC 8147](https://www.rfc-editor.org/rfc/rfc8174). \"
|
||||
}" > mattermost.json
|
||||
- uses: mattermost/action-mattermost-notify@master
|
||||
env:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
|
||||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'cryptography>=2.5.0',
|
||||
|
|
|
|||
|
|
@ -6,51 +6,48 @@ import unittest
|
|||
import pytest
|
||||
|
||||
|
||||
class JoseTest(unittest.TestCase):
|
||||
"""Tests for acme.jose shim."""
|
||||
def _test_it(submodule, attribute):
|
||||
if submodule:
|
||||
acme_jose_path = 'acme.jose.' + submodule
|
||||
josepy_path = 'josepy.' + submodule
|
||||
else:
|
||||
acme_jose_path = 'acme.jose'
|
||||
josepy_path = 'josepy'
|
||||
acme_jose_mod = importlib.import_module(acme_jose_path)
|
||||
josepy_mod = importlib.import_module(josepy_path)
|
||||
|
||||
def _test_it(self, submodule, attribute):
|
||||
if submodule:
|
||||
acme_jose_path = 'acme.jose.' + submodule
|
||||
josepy_path = 'josepy.' + submodule
|
||||
else:
|
||||
acme_jose_path = 'acme.jose'
|
||||
josepy_path = 'josepy'
|
||||
acme_jose_mod = importlib.import_module(acme_jose_path)
|
||||
josepy_mod = importlib.import_module(josepy_path)
|
||||
assert acme_jose_mod is josepy_mod
|
||||
assert getattr(acme_jose_mod, attribute) is getattr(josepy_mod, attribute)
|
||||
|
||||
assert acme_jose_mod is josepy_mod
|
||||
assert getattr(acme_jose_mod, attribute) is getattr(josepy_mod, attribute)
|
||||
# We use the imports below with eval, but pylint doesn't
|
||||
# understand that.
|
||||
import josepy # pylint: disable=unused-import
|
||||
|
||||
# We use the imports below with eval, but pylint doesn't
|
||||
# understand that.
|
||||
import josepy # pylint: disable=unused-import
|
||||
import acme # pylint: disable=unused-import
|
||||
acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used
|
||||
josepy_mod = eval(josepy_path) # pylint: disable=eval-used
|
||||
assert acme_jose_mod is josepy_mod
|
||||
assert getattr(acme_jose_mod, attribute) is getattr(josepy_mod, attribute)
|
||||
|
||||
import acme # pylint: disable=unused-import
|
||||
acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used
|
||||
josepy_mod = eval(josepy_path) # pylint: disable=eval-used
|
||||
assert acme_jose_mod is josepy_mod
|
||||
assert getattr(acme_jose_mod, attribute) is getattr(josepy_mod, attribute)
|
||||
def test_top_level():
|
||||
_test_it('', 'RS512')
|
||||
|
||||
def test_top_level(self):
|
||||
self._test_it('', 'RS512')
|
||||
def test_submodules():
|
||||
# This test ensures that the modules in josepy that were
|
||||
# available at the time it was moved into its own package are
|
||||
# available under acme.jose. Backwards compatibility with new
|
||||
# modules or testing code is not maintained.
|
||||
mods_and_attrs = [('b64', 'b64decode',),
|
||||
('errors', 'Error',),
|
||||
('interfaces', 'JSONDeSerializable',),
|
||||
('json_util', 'Field',),
|
||||
('jwa', 'HS256',),
|
||||
('jwk', 'JWK',),
|
||||
('jws', 'JWS',),
|
||||
('util', 'ImmutableMap',),]
|
||||
|
||||
def test_submodules(self):
|
||||
# This test ensures that the modules in josepy that were
|
||||
# available at the time it was moved into its own package are
|
||||
# available under acme.jose. Backwards compatibility with new
|
||||
# modules or testing code is not maintained.
|
||||
mods_and_attrs = [('b64', 'b64decode',),
|
||||
('errors', 'Error',),
|
||||
('interfaces', 'JSONDeSerializable',),
|
||||
('json_util', 'Field',),
|
||||
('jwa', 'HS256',),
|
||||
('jwk', 'JWK',),
|
||||
('jws', 'JWS',),
|
||||
('util', 'ImmutableMap',),]
|
||||
|
||||
for mod, attr in mods_and_attrs:
|
||||
self._test_it(mod, attr)
|
||||
for mod, attr in mods_and_attrs:
|
||||
_test_it(mod, attr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -5,14 +5,11 @@ import unittest
|
|||
import pytest
|
||||
|
||||
|
||||
class MapKeysTest(unittest.TestCase):
|
||||
"""Tests for acme.util.map_keys."""
|
||||
|
||||
def test_it(self):
|
||||
from acme.util import map_keys
|
||||
assert {'a': 'b', 'c': 'd'} == \
|
||||
map_keys({'a': 'b', 'c': 'd'}, lambda key: key)
|
||||
assert {2: 2, 4: 4} == map_keys({1: 2, 3: 4}, lambda x: x + 1)
|
||||
def test_it():
|
||||
from acme.util import map_keys
|
||||
assert {'a': 'b', 'c': 'd'} == \
|
||||
map_keys({'a': 'b', 'c': 'd'}, lambda key: key)
|
||||
assert {2: 2, 4: 4} == map_keys({1: 2, 3: 4}, lambda x: x + 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# We specify the minimum acme and certbot version as the current plugin
|
||||
|
|
|
|||
|
|
@ -136,8 +136,6 @@ class UseCorrectApacheExecutableTest(util.ApacheTest):
|
|||
class MultipleVhostsTestCentOS(util.ApacheTest):
|
||||
"""Multiple vhost tests for CentOS / RHEL family of distros"""
|
||||
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
@mock.patch("certbot.util.get_os_info")
|
||||
def setUp(self, mock_get_os_info): # pylint: disable=arguments-differ
|
||||
test_dir = "centos7_apache/apache"
|
||||
|
|
|
|||
|
|
@ -70,6 +70,13 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.config.prepare()
|
||||
|
||||
def test_prepare_locked(self):
|
||||
# It is important to test that server_root is locked during the call to
|
||||
# prepare (as opposed to somewhere else during plugin execution) to
|
||||
# ensure that this lock will be acquired after the Certbot package
|
||||
# acquires all of its locks. (Tests that Certbot calls prepare after
|
||||
# acquiring its locks are part of the Certbot package's tests.) Not
|
||||
# doing this could result in deadlock from two versions of Certbot that
|
||||
# acquire its locks in a different order.
|
||||
server_root = self.config.conf("server-root")
|
||||
self.config.config_test = mock.Mock()
|
||||
os.remove(os.path.join(server_root, ".certbot.lock"))
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ import util
|
|||
class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
"""Multiple vhost tests for Debian family of distros"""
|
||||
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super().setUp()
|
||||
self.config = util.get_apache_configurator(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Test for certbot_apache._internal.entrypoint for override class resolution"""
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
|
@ -9,40 +8,34 @@ from certbot_apache._internal import configurator
|
|||
from certbot_apache._internal import entrypoint
|
||||
|
||||
|
||||
class EntryPointTest(unittest.TestCase):
|
||||
"""Entrypoint tests"""
|
||||
def test_get_configurator():
|
||||
with mock.patch("certbot.util.get_os_info") as mock_info:
|
||||
for distro in entrypoint.OVERRIDE_CLASSES:
|
||||
return_value = (distro, "whatever")
|
||||
if distro == 'fedora_old':
|
||||
return_value = ('fedora', '28')
|
||||
elif distro == 'fedora':
|
||||
return_value = ('fedora', '29')
|
||||
mock_info.return_value = return_value
|
||||
assert entrypoint.get_configurator() == \
|
||||
entrypoint.OVERRIDE_CLASSES[distro]
|
||||
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def test_get_configurator(self):
|
||||
|
||||
with mock.patch("certbot.util.get_os_info") as mock_info:
|
||||
for distro in entrypoint.OVERRIDE_CLASSES:
|
||||
return_value = (distro, "whatever")
|
||||
if distro == 'fedora_old':
|
||||
return_value = ('fedora', '28')
|
||||
elif distro == 'fedora':
|
||||
return_value = ('fedora', '29')
|
||||
mock_info.return_value = return_value
|
||||
def test_nonexistent_like():
|
||||
with mock.patch("certbot.util.get_os_info") as mock_info:
|
||||
mock_info.return_value = ("nonexistent", "irrelevant")
|
||||
with mock.patch("certbot.util.get_systemd_os_like") as mock_like:
|
||||
for like in entrypoint.OVERRIDE_CLASSES:
|
||||
mock_like.return_value = [like]
|
||||
assert entrypoint.get_configurator() == \
|
||||
entrypoint.OVERRIDE_CLASSES[distro]
|
||||
entrypoint.OVERRIDE_CLASSES[like]
|
||||
|
||||
def test_nonexistent_like(self):
|
||||
with mock.patch("certbot.util.get_os_info") as mock_info:
|
||||
mock_info.return_value = ("nonexistent", "irrelevant")
|
||||
with mock.patch("certbot.util.get_systemd_os_like") as mock_like:
|
||||
for like in entrypoint.OVERRIDE_CLASSES:
|
||||
mock_like.return_value = [like]
|
||||
assert entrypoint.get_configurator() == \
|
||||
entrypoint.OVERRIDE_CLASSES[like]
|
||||
|
||||
def test_nonexistent_generic(self):
|
||||
with mock.patch("certbot.util.get_os_info") as mock_info:
|
||||
mock_info.return_value = ("nonexistent", "irrelevant")
|
||||
with mock.patch("certbot.util.get_systemd_os_like") as mock_like:
|
||||
mock_like.return_value = ["unknown"]
|
||||
assert entrypoint.get_configurator() == \
|
||||
configurator.ApacheConfigurator
|
||||
def test_nonexistent_generic():
|
||||
with mock.patch("certbot.util.get_os_info") as mock_info:
|
||||
mock_info.return_value = ("nonexistent", "irrelevant")
|
||||
with mock.patch("certbot.util.get_systemd_os_like") as mock_like:
|
||||
mock_like.return_value = ["unknown"]
|
||||
assert entrypoint.get_configurator() == \
|
||||
configurator.ApacheConfigurator
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -83,8 +83,6 @@ class FedoraRestartTest(util.ApacheTest):
|
|||
class MultipleVhostsTestFedora(util.ApacheTest):
|
||||
"""Multiple vhost tests for CentOS / RHEL family of distros"""
|
||||
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
test_dir = "centos7_apache/apache"
|
||||
config_root = "centos7_apache/apache/httpd"
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ def get_vh_truth(temp_dir, config_name):
|
|||
class MultipleVhostsTestGentoo(util.ApacheTest):
|
||||
"""Multiple vhost tests for non-debian distro"""
|
||||
|
||||
_multiprocess_can_split_ = True
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
test_dir = "gentoo_apache/apache"
|
||||
config_root = "gentoo_apache/apache/apache2"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
""" Tests for ParserNode interface """
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -104,27 +103,25 @@ interfaces.CommentNode.register(DummyCommentNode)
|
|||
interfaces.DirectiveNode.register(DummyDirectiveNode)
|
||||
interfaces.BlockNode.register(DummyBlockNode)
|
||||
|
||||
class ParserNodeTest(unittest.TestCase):
|
||||
def test_dummy():
|
||||
"""Dummy placeholder test case for ParserNode interfaces"""
|
||||
|
||||
def test_dummy(self):
|
||||
dummyblock = DummyBlockNode(
|
||||
name="None",
|
||||
parameters=(),
|
||||
ancestor=None,
|
||||
dirty=False,
|
||||
filepath="/some/random/path"
|
||||
)
|
||||
dummydirective = DummyDirectiveNode(
|
||||
name="Name",
|
||||
ancestor=None,
|
||||
filepath="/another/path"
|
||||
)
|
||||
dummycomment = DummyCommentNode(
|
||||
comment="Comment",
|
||||
ancestor=dummyblock,
|
||||
filepath="/some/file"
|
||||
)
|
||||
dummyblock = DummyBlockNode(
|
||||
name="None",
|
||||
parameters=(),
|
||||
ancestor=None,
|
||||
dirty=False,
|
||||
filepath="/some/random/path"
|
||||
)
|
||||
dummydirective = DummyDirectiveNode(
|
||||
name="Name",
|
||||
ancestor=None,
|
||||
filepath="/another/path"
|
||||
)
|
||||
dummycomment = DummyCommentNode(
|
||||
comment="Comment",
|
||||
ancestor=dummyblock,
|
||||
filepath="/some/file"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1,123 +1,119 @@
|
|||
""" Tests for ParserNode utils """
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from certbot_apache._internal import parsernode_util as util
|
||||
|
||||
|
||||
class ParserNodeUtilTest(unittest.TestCase):
|
||||
"""Tests for ParserNode utils"""
|
||||
def _setup_parsernode():
|
||||
""" Sets up kwargs dict for ParserNode """
|
||||
return {
|
||||
"ancestor": None,
|
||||
"dirty": False,
|
||||
"filepath": "/tmp",
|
||||
}
|
||||
|
||||
def _setup_parsernode(self):
|
||||
""" Sets up kwargs dict for ParserNode """
|
||||
return {
|
||||
"ancestor": None,
|
||||
"dirty": False,
|
||||
"filepath": "/tmp",
|
||||
}
|
||||
def _setup_commentnode():
|
||||
""" Sets up kwargs dict for CommentNode """
|
||||
|
||||
def _setup_commentnode(self):
|
||||
""" Sets up kwargs dict for CommentNode """
|
||||
pn = _setup_parsernode()
|
||||
pn["comment"] = "x"
|
||||
return pn
|
||||
|
||||
pn = self._setup_parsernode()
|
||||
pn["comment"] = "x"
|
||||
return pn
|
||||
def _setup_directivenode():
|
||||
""" Sets up kwargs dict for DirectiveNode """
|
||||
|
||||
def _setup_directivenode(self):
|
||||
""" Sets up kwargs dict for DirectiveNode """
|
||||
pn = _setup_parsernode()
|
||||
pn["name"] = "Name"
|
||||
pn["parameters"] = ("first",)
|
||||
pn["enabled"] = True
|
||||
return pn
|
||||
|
||||
pn = self._setup_parsernode()
|
||||
pn["name"] = "Name"
|
||||
pn["parameters"] = ("first",)
|
||||
pn["enabled"] = True
|
||||
return pn
|
||||
def test_unknown_parameter():
|
||||
params = _setup_parsernode()
|
||||
params["unknown"] = "unknown"
|
||||
with pytest.raises(TypeError):
|
||||
util.parsernode_kwargs(params)
|
||||
|
||||
def test_unknown_parameter(self):
|
||||
params = self._setup_parsernode()
|
||||
params["unknown"] = "unknown"
|
||||
with pytest.raises(TypeError):
|
||||
util.parsernode_kwargs(params)
|
||||
|
||||
params = self._setup_commentnode()
|
||||
params["unknown"] = "unknown"
|
||||
with pytest.raises(TypeError):
|
||||
util.commentnode_kwargs(params)
|
||||
|
||||
params = self._setup_directivenode()
|
||||
params["unknown"] = "unknown"
|
||||
with pytest.raises(TypeError):
|
||||
util.directivenode_kwargs(params)
|
||||
|
||||
def test_parsernode(self):
|
||||
params = self._setup_parsernode()
|
||||
ctrl = self._setup_parsernode()
|
||||
|
||||
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(params)
|
||||
assert ancestor == ctrl["ancestor"]
|
||||
assert dirty == ctrl["dirty"]
|
||||
assert filepath == ctrl["filepath"]
|
||||
assert metadata == {}
|
||||
|
||||
def test_parsernode_from_metadata(self):
|
||||
params = self._setup_parsernode()
|
||||
params.pop("filepath")
|
||||
md = {"some": "value"}
|
||||
params["metadata"] = md
|
||||
|
||||
# Just testing that error from missing required parameters is not raised
|
||||
_, _, _, metadata = util.parsernode_kwargs(params)
|
||||
assert metadata == md
|
||||
|
||||
def test_commentnode(self):
|
||||
params = self._setup_commentnode()
|
||||
ctrl = self._setup_commentnode()
|
||||
|
||||
comment, _ = util.commentnode_kwargs(params)
|
||||
assert comment == ctrl["comment"]
|
||||
|
||||
def test_commentnode_from_metadata(self):
|
||||
params = self._setup_commentnode()
|
||||
params.pop("comment")
|
||||
params["metadata"] = {}
|
||||
|
||||
# Just testing that error from missing required parameters is not raised
|
||||
params = _setup_commentnode()
|
||||
params["unknown"] = "unknown"
|
||||
with pytest.raises(TypeError):
|
||||
util.commentnode_kwargs(params)
|
||||
|
||||
def test_directivenode(self):
|
||||
params = self._setup_directivenode()
|
||||
ctrl = self._setup_directivenode()
|
||||
|
||||
name, parameters, enabled, _ = util.directivenode_kwargs(params)
|
||||
assert name == ctrl["name"]
|
||||
assert parameters == ctrl["parameters"]
|
||||
assert enabled == ctrl["enabled"]
|
||||
|
||||
def test_directivenode_from_metadata(self):
|
||||
params = self._setup_directivenode()
|
||||
params.pop("filepath")
|
||||
params.pop("name")
|
||||
params["metadata"] = {"irrelevant": "value"}
|
||||
|
||||
# Just testing that error from missing required parameters is not raised
|
||||
params = _setup_directivenode()
|
||||
params["unknown"] = "unknown"
|
||||
with pytest.raises(TypeError):
|
||||
util.directivenode_kwargs(params)
|
||||
|
||||
def test_missing_required(self):
|
||||
c_params = self._setup_commentnode()
|
||||
c_params.pop("comment")
|
||||
with pytest.raises(TypeError):
|
||||
util.commentnode_kwargs(c_params)
|
||||
def test_parsernode():
|
||||
params = _setup_parsernode()
|
||||
ctrl = _setup_parsernode()
|
||||
|
||||
d_params = self._setup_directivenode()
|
||||
d_params.pop("ancestor")
|
||||
with pytest.raises(TypeError):
|
||||
util.directivenode_kwargs(d_params)
|
||||
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(params)
|
||||
assert ancestor == ctrl["ancestor"]
|
||||
assert dirty == ctrl["dirty"]
|
||||
assert filepath == ctrl["filepath"]
|
||||
assert metadata == {}
|
||||
|
||||
p_params = self._setup_parsernode()
|
||||
p_params.pop("filepath")
|
||||
with pytest.raises(TypeError):
|
||||
util.parsernode_kwargs(p_params)
|
||||
def test_parsernode_from_metadata():
|
||||
params = _setup_parsernode()
|
||||
params.pop("filepath")
|
||||
md = {"some": "value"}
|
||||
params["metadata"] = md
|
||||
|
||||
# Just testing that error from missing required parameters is not raised
|
||||
_, _, _, metadata = util.parsernode_kwargs(params)
|
||||
assert metadata == md
|
||||
|
||||
def test_commentnode():
|
||||
params = _setup_commentnode()
|
||||
ctrl = _setup_commentnode()
|
||||
|
||||
comment, _ = util.commentnode_kwargs(params)
|
||||
assert comment == ctrl["comment"]
|
||||
|
||||
def test_commentnode_from_metadata():
|
||||
params = _setup_commentnode()
|
||||
params.pop("comment")
|
||||
params["metadata"] = {}
|
||||
|
||||
# Just testing that error from missing required parameters is not raised
|
||||
util.commentnode_kwargs(params)
|
||||
|
||||
def test_directivenode():
|
||||
params = _setup_directivenode()
|
||||
ctrl = _setup_directivenode()
|
||||
|
||||
name, parameters, enabled, _ = util.directivenode_kwargs(params)
|
||||
assert name == ctrl["name"]
|
||||
assert parameters == ctrl["parameters"]
|
||||
assert enabled == ctrl["enabled"]
|
||||
|
||||
def test_directivenode_from_metadata():
|
||||
params = _setup_directivenode()
|
||||
params.pop("filepath")
|
||||
params.pop("name")
|
||||
params["metadata"] = {"irrelevant": "value"}
|
||||
|
||||
# Just testing that error from missing required parameters is not raised
|
||||
util.directivenode_kwargs(params)
|
||||
|
||||
def test_missing_required():
|
||||
c_params = _setup_commentnode()
|
||||
c_params.pop("comment")
|
||||
with pytest.raises(TypeError):
|
||||
util.commentnode_kwargs(c_params)
|
||||
|
||||
d_params = _setup_directivenode()
|
||||
d_params.pop("ancestor")
|
||||
with pytest.raises(TypeError):
|
||||
util.directivenode_kwargs(d_params)
|
||||
|
||||
p_params = _setup_parsernode()
|
||||
p_params.pop("filepath")
|
||||
with pytest.raises(TypeError):
|
||||
util.parsernode_kwargs(p_params)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import re
|
|||
import subprocess
|
||||
import time
|
||||
from typing import Any
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@unittest.skipIf(os.name != 'nt', reason='Windows installer tests must be run on Windows.')
|
||||
@pytest.mark.skipif(os.name != 'nt', reason='Windows installer tests must be run on Windows.')
|
||||
def test_it(request: pytest.FixtureRequest) -> None:
|
||||
try:
|
||||
subprocess.check_call(['certbot', '--version'])
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'cloudflare>=1.5.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'python-digitalocean>=1.11', # 1.15.0 or newer is recommended for TTL support
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# This version of lexicon is required to address the problem described in
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'google-api-python-client>=1.5.5',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dnspython>=1.15.0',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'boto3>=1.15.15',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '2.4.0.dev0'
|
||||
version = '2.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# We specify the minimum acme and certbot version as the current plugin
|
||||
|
|
|
|||
|
|
@ -63,6 +63,13 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
assert (1, 6, 2) == self.config.version
|
||||
|
||||
def test_prepare_locked(self):
|
||||
# It is important to test that server_root is locked during the call to
|
||||
# prepare (as opposed to somewhere else during plugin execution) to
|
||||
# ensure that this lock will be acquired after the Certbot package
|
||||
# acquires all of its locks. (Tests that Certbot calls prepare after
|
||||
# acquiring its locks are part of the Certbot package's tests.) Not
|
||||
# doing this could result in deadlock from two versions of Certbot that
|
||||
# acquire its locks in a different order.
|
||||
server_root = self.config.conf("server-root")
|
||||
|
||||
from certbot import util as certbot_util
|
||||
|
|
|
|||
|
|
@ -2,7 +2,23 @@
|
|||
|
||||
Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## 2.4.0 - master
|
||||
## 2.5.0 - master
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
|
||||
### Changed
|
||||
|
||||
*
|
||||
|
||||
### Fixed
|
||||
|
||||
*
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 2.4.0 - 2023-03-07
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""Certbot client."""
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '2.4.0.dev0'
|
||||
__version__ = '2.5.0.dev0'
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ options:
|
|||
case, and to know when to deprecate support for past
|
||||
Python versions and flags. If you wish to hide this
|
||||
information from the Let's Encrypt server, set this to
|
||||
"". (default: CertbotACMEClient/2.3.0 (certbot;
|
||||
"". (default: CertbotACMEClient/2.4.0 (certbot;
|
||||
OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY
|
||||
(SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).
|
||||
The flags encoded in the user agent are: --duplicate,
|
||||
|
|
@ -295,8 +295,6 @@ manage:
|
|||
name)
|
||||
revoke Revoke a certificate specified with --cert-path or
|
||||
--cert-name
|
||||
update_symlinks Recreate symlinks in your /etc/letsencrypt/live/
|
||||
directory
|
||||
reconfigure Update renewal configuration for a certificate
|
||||
specified by --cert-name
|
||||
|
||||
|
|
@ -432,10 +430,6 @@ plugins:
|
|||
--authenticators Limit to authenticator plugins only. (default: None)
|
||||
--installers Limit to installer plugins only. (default: None)
|
||||
|
||||
update_symlinks:
|
||||
Recreates certificate and key symlinks in /etc/letsencrypt/live, if you
|
||||
changed them by hand or edited a renewal configuration file
|
||||
|
||||
enhance:
|
||||
Helps to harden the TLS configuration by adding security enhancements to
|
||||
already existing configuration.
|
||||
|
|
|
|||
|
|
@ -323,6 +323,7 @@ Porkbun_ Y N DNS Authentication for Porkbun
|
|||
Infomaniak_ Y N DNS Authentication using Infomaniak Domains API
|
||||
dns-multi_ Y N DNS authentication of 100+ providers using go-acme/lego
|
||||
dns-dnsmanager_ Y N DNS Authentication for dnsmanager.io
|
||||
standalone-nfq_ Y N HTTP Authentication that works with any webserver (Linux only)
|
||||
================== ==== ==== ===============================================================
|
||||
|
||||
.. _haproxy: https://github.com/greenhost/certbot-haproxy
|
||||
|
|
@ -347,6 +348,7 @@ dns-dnsmanager_ Y N DNS Authentication for dnsmanager.io
|
|||
.. _Infomaniak: https://github.com/Infomaniak/certbot-dns-infomaniak
|
||||
.. _dns-multi: https://github.com/alexzorin/certbot-dns-multi
|
||||
.. _dns-dnsmanager: https://github.com/stayallive/certbot-dns-dnsmanager
|
||||
.. _standalone-nfq: https://github.com/alexzorin/certbot-standalone-nfq
|
||||
|
||||
If you're interested, you can also :ref:`write your own plugin <dev-plugin>`.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Tests for certbot.compat.misc"""
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
|
@ -8,7 +7,7 @@ import pytest
|
|||
from certbot.compat import os
|
||||
|
||||
|
||||
class ExecuteStatusTest(unittest.TestCase):
|
||||
class ExecuteStatusTest:
|
||||
"""Tests for certbot.compat.misc.execute_command_status."""
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
"""Unit test for os module."""
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from certbot.compat import os
|
||||
|
||||
|
||||
class OsTest(unittest.TestCase):
|
||||
"""Unit tests for os module."""
|
||||
def test_forbidden_methods(self):
|
||||
# Checks for os module
|
||||
for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename',
|
||||
'replace', 'access', 'stat', 'fstat']:
|
||||
with pytest.raises(RuntimeError):
|
||||
getattr(os, method)()
|
||||
# Checks for os.path module
|
||||
for method in ['realpath']:
|
||||
with pytest.raises(RuntimeError):
|
||||
getattr(os.path, method)()
|
||||
def test_forbidden_methods():
|
||||
# Checks for os module
|
||||
for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename',
|
||||
'replace', 'access', 'stat', 'fstat']:
|
||||
with pytest.raises(RuntimeError):
|
||||
getattr(os, method)()
|
||||
# Checks for os.path module
|
||||
for method in ['realpath']:
|
||||
with pytest.raises(RuntimeError):
|
||||
getattr(os.path, method)()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import io
|
|||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
|
@ -12,89 +11,68 @@ from certbot import errors
|
|||
import certbot.tests.util as test_util
|
||||
|
||||
|
||||
class NotifyTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.notify"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_notify(self, mock_util):
|
||||
from certbot.display.util import notify
|
||||
notify("Hello World")
|
||||
mock_util().notification.assert_called_with(
|
||||
"Hello World", pause=False, decorate=False, wrap=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_notify(mock_util):
|
||||
from certbot.display.util import notify
|
||||
notify("Hello World")
|
||||
mock_util().notification.assert_called_with(
|
||||
"Hello World", pause=False, decorate=False, wrap=False
|
||||
)
|
||||
|
||||
|
||||
class NotificationTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.notification"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_notification(self, mock_util):
|
||||
from certbot.display.util import notification
|
||||
notification("Hello World")
|
||||
mock_util().notification.assert_called_with(
|
||||
"Hello World", pause=True, decorate=True, wrap=True, force_interactive=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_notification(mock_util):
|
||||
from certbot.display.util import notification
|
||||
notification("Hello World")
|
||||
mock_util().notification.assert_called_with(
|
||||
"Hello World", pause=True, decorate=True, wrap=True, force_interactive=False
|
||||
)
|
||||
|
||||
|
||||
class MenuTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.menu"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_menu(self, mock_util):
|
||||
from certbot.display.util import menu
|
||||
menu("Hello World", ["one", "two"], default=0)
|
||||
mock_util().menu.assert_called_with(
|
||||
"Hello World", ["one", "two"], default=0, cli_flag=None, force_interactive=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_menu(mock_util):
|
||||
from certbot.display.util import menu
|
||||
menu("Hello World", ["one", "two"], default=0)
|
||||
mock_util().menu.assert_called_with(
|
||||
"Hello World", ["one", "two"], default=0, cli_flag=None, force_interactive=False
|
||||
)
|
||||
|
||||
|
||||
class InputTextTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.input_text"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_input_text(self, mock_util):
|
||||
from certbot.display.util import input_text
|
||||
input_text("Hello World", default="something")
|
||||
mock_util().input.assert_called_with(
|
||||
"Hello World", default='something', cli_flag=None, force_interactive=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_input_text(mock_util):
|
||||
from certbot.display.util import input_text
|
||||
input_text("Hello World", default="something")
|
||||
mock_util().input.assert_called_with(
|
||||
"Hello World", default='something', cli_flag=None, force_interactive=False
|
||||
)
|
||||
|
||||
|
||||
class YesNoTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.yesno"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_yesno(self, mock_util):
|
||||
from certbot.display.util import yesno
|
||||
yesno("Hello World", default=True)
|
||||
mock_util().yesno.assert_called_with(
|
||||
"Hello World", yes_label='Yes', no_label='No', default=True, cli_flag=None,
|
||||
force_interactive=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_yesno(mock_util):
|
||||
from certbot.display.util import yesno
|
||||
yesno("Hello World", default=True)
|
||||
mock_util().yesno.assert_called_with(
|
||||
"Hello World", yes_label='Yes', no_label='No', default=True, cli_flag=None,
|
||||
force_interactive=False
|
||||
)
|
||||
|
||||
|
||||
class ChecklistTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.checklist"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_checklist(self, mock_util):
|
||||
from certbot.display.util import checklist
|
||||
checklist("Hello World", ["one", "two"], default="one")
|
||||
mock_util().checklist.assert_called_with(
|
||||
"Hello World", ['one', 'two'], default='one', cli_flag=None, force_interactive=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_checklist(mock_util):
|
||||
from certbot.display.util import checklist
|
||||
checklist("Hello World", ["one", "two"], default="one")
|
||||
mock_util().checklist.assert_called_with(
|
||||
"Hello World", ['one', 'two'], default='one', cli_flag=None, force_interactive=False
|
||||
)
|
||||
|
||||
|
||||
class DirectorySelectTest(unittest.TestCase):
|
||||
"""Tests for certbot.display.util.directory_select"""
|
||||
|
||||
@test_util.patch_display_util()
|
||||
def test_directory_select(self, mock_util):
|
||||
from certbot.display.util import directory_select
|
||||
directory_select("Hello World", default="something")
|
||||
mock_util().directory_select.assert_called_with(
|
||||
"Hello World", default='something', cli_flag=None, force_interactive=False
|
||||
)
|
||||
@test_util.patch_display_util()
|
||||
def test_directory_select(mock_util):
|
||||
from certbot.display.util import directory_select
|
||||
directory_select("Hello World", default="something")
|
||||
mock_util().directory_select.assert_called_with(
|
||||
"Hello World", default='something', cli_flag=None, force_interactive=False
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Tests for certbot.helpful_parser"""
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
|
@ -11,7 +10,7 @@ from certbot._internal.cli import _DomainsAction
|
|||
from certbot._internal.cli import HelpfulArgumentParser
|
||||
|
||||
|
||||
class TestScanningFlags(unittest.TestCase):
|
||||
class TestScanningFlags:
|
||||
'''Test the prescan_for_flag method of HelpfulArgumentParser'''
|
||||
def test_prescan_no_help_flag(self):
|
||||
arg_parser = HelpfulArgumentParser(['run'], {})
|
||||
|
|
@ -40,7 +39,7 @@ class TestScanningFlags(unittest.TestCase):
|
|||
arg_parser.help_topics)
|
||||
assert detected_flag is False
|
||||
|
||||
class TestDetermineVerbs(unittest.TestCase):
|
||||
class TestDetermineVerbs:
|
||||
'''Tests for determine_verb methods of HelpfulArgumentParser'''
|
||||
def test_determine_verb_wrong_verb(self):
|
||||
arg_parser = HelpfulArgumentParser(['potato'], {})
|
||||
|
|
@ -71,7 +70,7 @@ class TestDetermineVerbs(unittest.TestCase):
|
|||
assert arg_parser.args == []
|
||||
|
||||
|
||||
class TestAdd(unittest.TestCase):
|
||||
class TestAdd:
|
||||
'''Tests for add method in HelpfulArgumentParser'''
|
||||
def test_add_trivial_argument(self):
|
||||
arg_parser = HelpfulArgumentParser(['run'], {})
|
||||
|
|
@ -93,7 +92,7 @@ class TestAdd(unittest.TestCase):
|
|||
assert hasattr(parsed_args, 'eab_kid')
|
||||
|
||||
|
||||
class TestAddGroup(unittest.TestCase):
|
||||
class TestAddGroup:
|
||||
'''Test add_group method of HelpfulArgumentParser'''
|
||||
def test_add_group_no_input(self):
|
||||
arg_parser = HelpfulArgumentParser(['run'], {})
|
||||
|
|
@ -118,7 +117,7 @@ class TestAddGroup(unittest.TestCase):
|
|||
assert arg_parser.groups["certonly"] is False
|
||||
|
||||
|
||||
class TestParseArgsErrors(unittest.TestCase):
|
||||
class TestParseArgsErrors:
|
||||
'''Tests for errors that should be met for some cases in parse_args method
|
||||
in HelpfulArgumentParser'''
|
||||
def test_parse_args_renew_force_interactive(self):
|
||||
|
|
@ -194,7 +193,7 @@ class TestParseArgsErrors(unittest.TestCase):
|
|||
arg_parser.parse_args()
|
||||
|
||||
|
||||
class TestAddDeprecatedArgument(unittest.TestCase):
|
||||
class TestAddDeprecatedArgument:
|
||||
"""Tests for add_deprecated_argument method of HelpfulArgumentParser"""
|
||||
|
||||
@mock.patch.object(HelpfulArgumentParser, "modify_kwargs_for_default_detection")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# coding=utf-8
|
||||
"""Tests for certbot._internal.main."""
|
||||
# pylint: disable=too-many-lines
|
||||
import contextlib
|
||||
import datetime
|
||||
from importlib import reload as reload_module
|
||||
import io
|
||||
|
|
@ -33,6 +34,7 @@ from certbot._internal import updater
|
|||
from certbot._internal.plugins import disco
|
||||
from certbot._internal.plugins import manual
|
||||
from certbot._internal.plugins import null
|
||||
from certbot._internal.plugins import standalone
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
from certbot.plugins import enhancements
|
||||
|
|
@ -2446,5 +2448,80 @@ class ShowAccountTest(test_util.ConfigTestCase):
|
|||
' Email contacts: foo@example.com, bar@example.com')])
|
||||
|
||||
|
||||
class TestLockOrder:
|
||||
"""Tests that Certbot's directory locks were acquired in the right order."""
|
||||
EXPECTED_ERROR_TYPE = errors.Error
|
||||
EXPECTED_ERROR_STR = 'Expected TestLockOrder error'
|
||||
# This regex is needed because certbot renew captures raised errors and
|
||||
# raises its own.
|
||||
EXPECTED_ERROR_STR_REGEX = f'{EXPECTED_ERROR_STR}|1 renew failure'
|
||||
|
||||
@pytest.fixture
|
||||
def mock_lock_dir(self):
|
||||
with mock.patch('certbot._internal.lock.lock_dir') as mock_lock_dir:
|
||||
yield mock_lock_dir
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mock_plugin_prepare(self, authenticator_dir, installer_dir, mock_lock_dir, subcommand):
|
||||
"""Patches plugin prepare to call mock_lock_dir and raise the expected error."""
|
||||
def authenticator_lock(unused_self):
|
||||
mock_lock_dir(authenticator_dir)
|
||||
raise self.EXPECTED_ERROR_TYPE(self.EXPECTED_ERROR_STR)
|
||||
|
||||
def installer_lock(unused_self):
|
||||
mock_lock_dir(installer_dir)
|
||||
# Unless an installer isn't needed (e.g. certbot install), we
|
||||
# expect the authenticator to raise the expected error because it
|
||||
# is prepared last. See
|
||||
# https://github.com/certbot/certbot/blob/7a6752a68ed77e73c2b29ab20d3ca8927f4fa7b0/certbot/certbot/_internal/plugins/selection.py#L246-L249
|
||||
if subcommand == 'install':
|
||||
raise self.EXPECTED_ERROR_TYPE(self.EXPECTED_ERROR_STR)
|
||||
|
||||
with mock.patch.object(standalone.Authenticator, 'prepare', authenticator_lock):
|
||||
with mock.patch.object(null.Installer, 'prepare', installer_lock):
|
||||
yield
|
||||
|
||||
@pytest.fixture(params='certonly install renew run'.split())
|
||||
def args_and_lock_order(self, mock_lock_dir, request, tmp_path):
|
||||
"""Sets up Certbot with args and mocks to error after acquiring the last lock.
|
||||
|
||||
This fixture yields the CLI arguments that should be given to Certbot
|
||||
and the expected order of directories to be locked. An error is raised
|
||||
after acquiring the last lock just as a means of stopping Certbot's
|
||||
execution.
|
||||
|
||||
"""
|
||||
# select directories
|
||||
authenticator_dir = str(tmp_path / 'authenticator')
|
||||
config_dir = str(tmp_path / 'config')
|
||||
installer_dir = str(tmp_path / 'installer')
|
||||
logs_dir = str(tmp_path / 'logs')
|
||||
work_dir = str(tmp_path / 'work')
|
||||
|
||||
# prepare args and lineage
|
||||
subcommand = request.param
|
||||
args = [subcommand, '-a', 'standalone', '-i', 'null', '--no-random-sleep-on-renew',
|
||||
'--config-dir', config_dir, '--logs-dir', logs_dir, '--work-dir',
|
||||
work_dir]
|
||||
test_util.make_lineage(config_dir, 'sample-renewal.conf')
|
||||
|
||||
with self.mock_plugin_prepare(authenticator_dir, installer_dir, mock_lock_dir, subcommand):
|
||||
lock_order = [logs_dir, config_dir, work_dir, installer_dir]
|
||||
if subcommand == 'install':
|
||||
yield args, lock_order
|
||||
else:
|
||||
# We expect the installer to be prepared even for certonly
|
||||
# because an installer was requested on the command line.
|
||||
yield args, lock_order + [authenticator_dir]
|
||||
|
||||
def test_lock_order(self, args_and_lock_order, mock_lock_dir):
|
||||
args, lock_order = args_and_lock_order
|
||||
with pytest.raises(self.EXPECTED_ERROR_TYPE, match=self.EXPECTED_ERROR_STR_REGEX):
|
||||
main.main(args)
|
||||
assert mock_lock_dir.call_count == len(lock_order)
|
||||
for call, locked_dir in zip(mock_lock_dir.call_args_list, lock_order):
|
||||
assert call[0][0] == locked_dir
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Tests for certbot.plugins.util."""
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
|
@ -8,38 +7,33 @@ import pytest
|
|||
from certbot.compat import os
|
||||
|
||||
|
||||
class GetPrefixTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.get_prefixes."""
|
||||
def test_get_prefix(self):
|
||||
from certbot.plugins.util import get_prefixes
|
||||
assert get_prefixes('/a/b/c') == \
|
||||
[os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']]
|
||||
assert get_prefixes('/') == [os.path.normpath('/')]
|
||||
assert get_prefixes('a') == ['a']
|
||||
def test_get_prefix():
|
||||
from certbot.plugins.util import get_prefixes
|
||||
assert get_prefixes('/a/b/c') == \
|
||||
[os.path.normpath(path) for path in ['/a/b/c', '/a/b', '/a', '/']]
|
||||
assert get_prefixes('/') == [os.path.normpath('/')]
|
||||
assert get_prefixes('a') == ['a']
|
||||
|
||||
|
||||
class PathSurgeryTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.path_surgery."""
|
||||
|
||||
@mock.patch("certbot.plugins.util.logger.debug")
|
||||
def test_path_surgery(self, mock_debug):
|
||||
from certbot.plugins.util import path_surgery
|
||||
all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"}
|
||||
with mock.patch.dict('os.environ', all_path):
|
||||
with mock.patch('certbot.util.exe_exists') as mock_exists:
|
||||
mock_exists.return_value = True
|
||||
assert path_surgery("eg") is True
|
||||
assert mock_debug.call_count == 0
|
||||
assert os.environ["PATH"] == all_path["PATH"]
|
||||
if os.name != 'nt':
|
||||
# This part is specific to Linux since on Windows no PATH surgery is ever done.
|
||||
no_path = {"PATH": "/tmp/"}
|
||||
with mock.patch.dict('os.environ', no_path):
|
||||
path_surgery("thingy")
|
||||
assert mock_debug.call_count == (2 if os.name != 'nt' else 1)
|
||||
assert "Failed to find" in mock_debug.call_args[0][0]
|
||||
assert "/usr/local/bin" in os.environ["PATH"]
|
||||
assert "/tmp" in os.environ["PATH"]
|
||||
@mock.patch("certbot.plugins.util.logger.debug")
|
||||
def test_path_surgery(mock_debug):
|
||||
from certbot.plugins.util import path_surgery
|
||||
all_path = {"PATH": "/usr/local/bin:/bin/:/usr/sbin/:/usr/local/sbin/"}
|
||||
with mock.patch.dict('os.environ', all_path):
|
||||
with mock.patch('certbot.util.exe_exists') as mock_exists:
|
||||
mock_exists.return_value = True
|
||||
assert path_surgery("eg") is True
|
||||
assert mock_debug.call_count == 0
|
||||
assert os.environ["PATH"] == all_path["PATH"]
|
||||
if os.name != 'nt':
|
||||
# This part is specific to Linux since on Windows no PATH surgery is ever done.
|
||||
no_path = {"PATH": "/tmp/"}
|
||||
with mock.patch.dict('os.environ', no_path):
|
||||
path_surgery("thingy")
|
||||
assert mock_debug.call_count == (2 if os.name != 'nt' else 1)
|
||||
assert "Failed to find" in mock_debug.call_args[0][0]
|
||||
assert "/usr/local/bin" in os.environ["PATH"]
|
||||
assert "/tmp" in os.environ["PATH"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ WHITELIST_PATHS = [
|
|||
'/acme/acme/',
|
||||
'/certbot-ci/',
|
||||
'/certbot-compatibility-test/',
|
||||
'/tests/lock_test',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,12 @@
|
|||
# updated.
|
||||
# 4) Ignore our own PendingDeprecationWarning about update_symlinks soon to be dropped.
|
||||
# See https://github.com/certbot/certbot/issues/6284.
|
||||
# 5) Ignore pkg_resources.declare_namespace used in a large number of Google's Python
|
||||
# libs. e.g. https://github.com/googleapis/google-auth-library-python/issues/1229.
|
||||
filterwarnings =
|
||||
error
|
||||
ignore:decodestring\(\) is a deprecated alias:DeprecationWarning:dns
|
||||
ignore:.*rsyncdir:DeprecationWarning
|
||||
ignore:'urllib3.contrib.pyopenssl:DeprecationWarning:requests_toolbelt
|
||||
ignore:update_symlinks is deprecated:PendingDeprecationWarning
|
||||
ignore:.*declare_namespace\('google:DeprecationWarning
|
||||
|
|
|
|||
|
|
@ -1,296 +0,0 @@
|
|||
"""Tests to ensure the lock order is preserved."""
|
||||
import atexit
|
||||
import datetime
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
from certbot import util
|
||||
from certbot._internal import lock
|
||||
from certbot.compat import filesystem
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the lock tests."""
|
||||
dirs, base_cmd = set_up()
|
||||
for subcommand in ('certonly', 'install', 'renew', 'run',):
|
||||
logger.info('Testing subcommand: %s', subcommand)
|
||||
test_command(base_cmd + [subcommand], dirs)
|
||||
logger.info('Lock test ran successfully.')
|
||||
|
||||
|
||||
def set_up() -> Tuple[List[str], List[str]]:
|
||||
"""Prepare tests to be run.
|
||||
|
||||
Logging is set up and temporary directories are set up to contain a
|
||||
basic Certbot and Nginx configuration. The directories are returned
|
||||
in the order they should be locked by Certbot. If the Nginx plugin
|
||||
is expected to work on the system, the Nginx directory is included,
|
||||
otherwise, it is not.
|
||||
|
||||
A Certbot command is also created that uses the temporary
|
||||
directories. The returned command can be used to test different
|
||||
subcommands by appending the desired command to the end.
|
||||
|
||||
:returns: directories and command
|
||||
:rtype: `tuple` of `list`
|
||||
|
||||
"""
|
||||
logging.basicConfig(format='%(message)s', level=logging.INFO)
|
||||
config_dir, logs_dir, work_dir, nginx_dir = set_up_dirs()
|
||||
command = set_up_command(config_dir, logs_dir, work_dir, nginx_dir)
|
||||
|
||||
dirs = [logs_dir, config_dir, work_dir]
|
||||
# If Nginx is installed, do the test, otherwise skip it.
|
||||
# Issue https://github.com/certbot/certbot/issues/8121 tracks the work to remove this control.
|
||||
if util.exe_exists('nginx'):
|
||||
dirs.append(nginx_dir)
|
||||
else:
|
||||
logger.warning('Skipping Nginx lock tests')
|
||||
|
||||
return dirs, command
|
||||
|
||||
|
||||
def set_up_dirs() -> Tuple[str, str, str, str]:
|
||||
"""Set up directories for tests.
|
||||
|
||||
A temporary directory is created to contain the config, log, work,
|
||||
and nginx directories. A sample renewal configuration is created in
|
||||
the config directory and a basic Nginx config is placed in the Nginx
|
||||
directory. The temporary directory containing all of these
|
||||
directories is deleted when the program exits.
|
||||
|
||||
:return value: config, log, work, and nginx directories
|
||||
:rtype: `tuple` of `str`
|
||||
|
||||
"""
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
logger.debug('Created temporary directory: %s', temp_dir)
|
||||
atexit.register(functools.partial(shutil.rmtree, temp_dir))
|
||||
|
||||
config_dir = os.path.join(temp_dir, 'config')
|
||||
logs_dir = os.path.join(temp_dir, 'logs')
|
||||
work_dir = os.path.join(temp_dir, 'work')
|
||||
nginx_dir = os.path.join(temp_dir, 'nginx')
|
||||
|
||||
for directory in (config_dir, logs_dir, work_dir, nginx_dir,):
|
||||
filesystem.mkdir(directory)
|
||||
|
||||
test_util.make_lineage(config_dir, 'sample-renewal.conf')
|
||||
set_up_nginx_dir(nginx_dir)
|
||||
|
||||
return config_dir, logs_dir, work_dir, nginx_dir
|
||||
|
||||
|
||||
def set_up_nginx_dir(root_path: str) -> None:
|
||||
"""Create a basic Nginx configuration in nginx_dir.
|
||||
|
||||
:param str root_path: where the Nginx server root should be placed
|
||||
|
||||
"""
|
||||
# Get the root of the git repository
|
||||
repo_root = check_call('git rev-parse --show-toplevel'.split()).strip()
|
||||
conf_script = os.path.join(
|
||||
repo_root, 'certbot-nginx', 'tests', 'boulder-integration.conf.sh')
|
||||
# Prepare self-signed certificates for Nginx
|
||||
key_path, cert_path = setup_certificate(root_path)
|
||||
# Generate Nginx configuration
|
||||
with open(os.path.join(root_path, 'nginx.conf'), 'w') as f:
|
||||
f.write(check_call(['/bin/sh', conf_script, root_path, key_path, cert_path]))
|
||||
|
||||
|
||||
def set_up_command(config_dir: str, logs_dir: str, work_dir: str, nginx_dir: str) -> List[str]:
|
||||
"""Build the Certbot command to run for testing.
|
||||
|
||||
You can test different subcommands by appending the desired command
|
||||
to the returned list.
|
||||
|
||||
:param str config_dir: path to the configuration directory
|
||||
:param str logs_dir: path to the logs directory
|
||||
:param str work_dir: path to the work directory
|
||||
:param str nginx_dir: path to the nginx directory
|
||||
|
||||
:returns: certbot command to execute for testing
|
||||
:rtype: `list` of `str`
|
||||
|
||||
"""
|
||||
return (
|
||||
'certbot --cert-path {0} --key-path {1} --config-dir {2} '
|
||||
'--logs-dir {3} --work-dir {4} --nginx-server-root {5} --debug '
|
||||
'--force-renewal --nginx -vv --no-random-sleep-on-renew '.format(
|
||||
test_util.vector_path('cert.pem'),
|
||||
test_util.vector_path('rsa512_key.pem'),
|
||||
config_dir, logs_dir, work_dir, nginx_dir).split())
|
||||
|
||||
|
||||
def setup_certificate(workspace: str) -> Tuple[str, str]:
|
||||
"""Generate a self-signed certificate for nginx.
|
||||
:param workspace: path of folder where to put the certificate
|
||||
:return: tuple containing the key path and certificate path
|
||||
:rtype: `tuple`
|
||||
"""
|
||||
# Generate key
|
||||
private_key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend()
|
||||
)
|
||||
subject = issuer = x509.Name([
|
||||
x509.NameAttribute(x509.NameOID.COMMON_NAME, 'nginx.wtf')
|
||||
])
|
||||
certificate = x509.CertificateBuilder().subject_name(
|
||||
subject
|
||||
).issuer_name(
|
||||
issuer
|
||||
).public_key(
|
||||
private_key.public_key()
|
||||
).serial_number(
|
||||
1
|
||||
).not_valid_before(
|
||||
datetime.datetime.utcnow()
|
||||
).not_valid_after(
|
||||
datetime.datetime.utcnow() + datetime.timedelta(days=1)
|
||||
).sign(private_key, hashes.SHA256(), default_backend())
|
||||
|
||||
key_path = os.path.join(workspace, 'cert.key')
|
||||
with open(key_path, 'wb') as file_handle:
|
||||
file_handle.write(private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
))
|
||||
|
||||
cert_path = os.path.join(workspace, 'cert.pem')
|
||||
with open(cert_path, 'wb') as file_handle:
|
||||
file_handle.write(certificate.public_bytes(serialization.Encoding.PEM))
|
||||
|
||||
return key_path, cert_path
|
||||
|
||||
|
||||
def test_command(command: List[str], directories: Iterable[str]) -> None:
|
||||
"""Assert Certbot acquires locks in a specific order.
|
||||
|
||||
command is run repeatedly testing that Certbot acquires locks on
|
||||
directories in the order they appear in the parameter directories.
|
||||
|
||||
:param list command: Certbot command to execute
|
||||
:param list directories: list of directories Certbot should fail
|
||||
to acquire the lock on in sorted order
|
||||
|
||||
"""
|
||||
locks = [lock.lock_dir(directory) for directory in directories]
|
||||
for dir_path, dir_lock in zip(directories, locks):
|
||||
check_error(command, dir_path)
|
||||
dir_lock.release()
|
||||
|
||||
|
||||
def check_error(command: List[str], dir_path: str) -> None:
|
||||
"""Run command and verify it fails to acquire the lock for dir_path.
|
||||
|
||||
:param str command: certbot command to run
|
||||
:param str dir_path: path to directory containing the lock Certbot
|
||||
should fail on
|
||||
|
||||
"""
|
||||
ret, out, err = subprocess_call(command)
|
||||
if ret == 0:
|
||||
report_failure("Certbot didn't exit with a nonzero status!", out, err)
|
||||
|
||||
# this regex looks weird in order to deal with a text change between HEAD and -oldest
|
||||
match = re.search("ee the logfile '?(.*?)'? ", err)
|
||||
if match is not None:
|
||||
# Get error output from more verbose logfile
|
||||
with open(match.group(1)) as f:
|
||||
err = f.read()
|
||||
|
||||
pattern = 'A lock on {0}.* is held by another process'.format(dir_path)
|
||||
if not re.search(pattern, err):
|
||||
err_msg = 'Directory path {0} not in error output!'.format(dir_path)
|
||||
report_failure(err_msg, out, err)
|
||||
|
||||
|
||||
def check_call(args: List[str]) -> str:
|
||||
"""Simple imitation of subprocess.check_call.
|
||||
|
||||
This function is only available in subprocess in Python 2.7+.
|
||||
|
||||
:param list args: program and it's arguments to be run
|
||||
|
||||
:returns: stdout output
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
exit_code, out, err = subprocess_call(args)
|
||||
if exit_code:
|
||||
report_failure('Command exited with a nonzero status!', out, err)
|
||||
return out
|
||||
|
||||
|
||||
def report_failure(err_msg: str, out: str, err: str) -> None:
|
||||
"""Report a subprocess failure and exit.
|
||||
|
||||
:param str err_msg: error message to report
|
||||
:param str out: stdout output
|
||||
:param str err: stderr output
|
||||
|
||||
"""
|
||||
logger.critical(err_msg)
|
||||
log_output(logging.INFO, out, err)
|
||||
sys.exit(err_msg)
|
||||
|
||||
|
||||
def subprocess_call(args: List[str]) -> Tuple[int, str, str]:
|
||||
"""Run a command with subprocess and return the result.
|
||||
|
||||
:param list args: program and it's arguments to be run
|
||||
|
||||
:returns: return code, stdout output, stderr output
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
process = subprocess.run(args, stdout=subprocess.PIPE, check=False,
|
||||
stderr=subprocess.PIPE, universal_newlines=True)
|
||||
out, err = process.stdout, process.stderr
|
||||
logger.debug('Return code was %d', process.returncode)
|
||||
log_output(logging.DEBUG, out, err)
|
||||
return process.returncode, out, err
|
||||
|
||||
|
||||
def log_output(level: int, out: str, err: str) -> None:
|
||||
"""Logs stdout and stderr output at the requested level.
|
||||
|
||||
:param int level: logging level to use
|
||||
:param str out: stdout output
|
||||
:param str err: stderr output
|
||||
|
||||
"""
|
||||
if out:
|
||||
logger.log(level, 'Stdout output was:\n%s', out)
|
||||
if err:
|
||||
logger.log(level, 'Stderr output was:\n%s', err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if os.name != 'nt':
|
||||
main()
|
||||
else:
|
||||
print(
|
||||
'Warning: lock_test cannot be executed on Windows, '
|
||||
'as it relies on a Nginx distribution for Linux.')
|
||||
4
tox.ini
4
tox.ini
|
|
@ -20,7 +20,7 @@ install_and_test = python {toxinidir}/tools/install_and_test.py
|
|||
dns_packages = certbot-dns-cloudflare certbot-dns-digitalocean certbot-dns-dnsimple certbot-dns-dnsmadeeasy certbot-dns-gehirn certbot-dns-google certbot-dns-linode certbot-dns-luadns certbot-dns-nsone certbot-dns-ovh certbot-dns-rfc2136 certbot-dns-route53 certbot-dns-sakuracloud
|
||||
win_all_packages = acme[test] certbot[test] {[base]dns_packages} certbot-nginx
|
||||
all_packages = {[base]win_all_packages} certbot-apache
|
||||
source_paths = acme/acme certbot/certbot certbot-apache/certbot_apache certbot-ci/certbot_integration_tests certbot-ci/snap_integration_tests certbot-ci/windows_installer_integration_tests certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx tests/lock_test.py
|
||||
source_paths = acme/acme certbot/certbot certbot-apache/certbot_apache certbot-ci/certbot_integration_tests certbot-ci/snap_integration_tests certbot-ci/windows_installer_integration_tests certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx
|
||||
|
||||
[testenv]
|
||||
passenv =
|
||||
|
|
@ -32,7 +32,6 @@ commands_pre = python {toxinidir}/tools/pipstrap.py
|
|||
commands =
|
||||
win: {[base]install_and_test} {[base]win_all_packages}
|
||||
!win: {[base]install_and_test} {[base]all_packages}
|
||||
python tests/lock_test.py
|
||||
# We always recreate the virtual environment to avoid problems like
|
||||
# https://github.com/certbot/certbot/issues/7745.
|
||||
recreate = true
|
||||
|
|
@ -104,7 +103,6 @@ basepython =
|
|||
commands =
|
||||
{[base]pip_install} acme[test] certbot[test] certbot-nginx
|
||||
pytest certbot-nginx
|
||||
python tests/lock_test.py
|
||||
setenv =
|
||||
{[testenv:oldest]setenv}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue