Compare commits

...

8 Commits

Author SHA1 Message Date
Scott Armitage
a09be984e1 Add function docstring to appease lint 2018-07-09 16:22:48 -07:00
Scott Armitage
3fe338a35a Wrap argparse_type in list if nargs produces one
In argparse, type casting is performed on individual arguments, which
may or may not be aggregated into a list depending on other factors,
normally the value given to `nargs`. When `nargs` is one of the values
that causes argparse to return a list, return a wrapping function that
will serialize an iterable, calling `action.type` (or default `str`)
on each element of the iterable as opposed to on the iterable itself.
2018-07-09 15:49:30 -07:00
Scott Armitage
15904d24c0 Fix detect defaults when nargs present
If a plugin adds a command line argument with the `nargs` parameter, e.g. `nargs='+'`, this parameter gets filtered from the `HelpfulArgumentParser` created/used in default detection. As a result, this parser does not parse the command line in the expected way, instead using the default value for `nargs`.

Leaving `nargs` in place resolves this issue.
2018-07-09 15:29:20 -07:00
Brad Warren
43f2bfd6f1 Advertise our packages work on Python 3.7. (#6183) 2018-07-09 09:17:03 -07:00
Brad Warren
cdf93de338 Full Python 3.7 support (#6182)
Now that yaml/pyyaml#126 is resolved, #6170 can be reverted by bumping the pinned version of PyYAML.

You can see this code passing with full macOS and integration tests at https://travis-ci.org/certbot/certbot/builds/400957729.

* Revert "Allow py37 testing (#6170)"

This reverts commit cad95466b0.

* Bump pyyaml pinning to work on Python 3.7.
2018-07-09 09:16:44 -07:00
Brad Warren
dd600db436 Upgrade pinned josepy version (#6184)
We released josepy 1.1.0 a while ago to work around newer versions of cryptography deprecating some of the functionality we were using. We haven't yet upgraded our pinned josepy version though and since #6169 has landed, we're now seeing these deprecation warnings in our tests. This would be shown to certbot-auto users as well.

This PR removes these warnings by upgrading our pinned version of josepy.

* update pinned josepy version

* build leauto

* update pinned dev version of josepy
2018-07-09 09:16:08 -07:00
Joona Hoikkala
2564566e1c Do not call IPlugin.prepare() for updaters when running renew (#6167)
interfaces.GenericUpdater and new enhancement interface updater functions get run on every invocation of Certbot with "renew" verb for every lineage. This causes performance problems for users with large configurations, because of plugin plumbing and preparsing happening in prepare() method of installer plugins. This PR moves the responsibility to call prepare() to the plugin (possibly) implementing a new style enhancement interface.

Fixes: #6153

* Do not call IPlugin.prepare() for updaters when running renew

* Check prepare called in tests

* Refine pydoc and make the function name more informative

* Verify the plugin type
2018-07-06 13:19:29 -07:00
Brad Warren
08378203df Add Python 3.7 tests (#6179)
* Remove apacheconftest packages.

The apacheconftests handle installing Apache dependencies, so let's remove it from the general case.

* We don't need to run dpkg -s in before_install.

* Remove augeas sources.

We only needed it for Ubuntu Precise which is dead and it doesn't work in Ubuntu Xenial.

* Upgrade Python 3.6 tests to 3.7.

Let's continue the approach of testing on the oldest and newest versions of Python 3. We will continue testing on Python 3.6 in the nightly tests.

* Revert "We don't need to run dpkg -s in before_install."

This reverts commit e5d35099a7.

* let apacheconftest handle deps
2018-07-06 19:08:40 +03:00
34 changed files with 166 additions and 63 deletions

View File

@@ -41,8 +41,9 @@ matrix:
env: TOXENV=py34
sudo: required
services: docker
- python: "3.6"
env: TOXENV=py36
- python: "3.7"
dist: xenial
env: TOXENV=py37
sudo: required
services: docker
- sudo: required
@@ -77,8 +78,6 @@ sudo: false
addons:
apt:
sources:
- augeas
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
- python-dev
- python-virtualenv
@@ -90,10 +89,6 @@ addons:
# For certbot-nginx integration testing
- nginx-light
- openssl
# for apacheconftest
- apache2
- libapache2-mod-wsgi
- libapache2-mod-macro
install: "travis_retry $(command -v pip || command -v pip3) install tox coveralls"
script:

View File

@@ -68,6 +68,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],

View File

@@ -165,6 +165,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]]
# These will be set in the prepare function
self._prepared = False
self.parser = None
self.version = version
self.vhosts = None
@@ -249,6 +250,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logger.debug("Encountered error:", exc_info=True)
raise errors.PluginError(
"Unable to lock %s", self.conf("server-root"))
self._prepared = True
def _check_aug_version(self):
""" Checks that we have recent enough version of libaugeas.
@@ -2394,6 +2396,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
continue
nextstep = config["laststep"] + 1
if nextstep < len(constants.AUTOHSTS_STEPS):
# If installer hasn't been prepared yet, do it now
if not self._prepared:
self.prepare()
# Have not reached the max value yet
try:
vhost = self.find_vhost_by_id(id_str)

View File

@@ -46,6 +46,7 @@ function Cleanup() {
# if our environment asks us to enable modules, do our best!
if [ "$1" = --debian-modules ] ; then
sudo apt-get install -y apache2
sudo apt-get install -y libapache2-mod-wsgi
sudo apt-get install -y libapache2-mod-macro

View File

@@ -55,7 +55,9 @@ class AutoHSTSTest(util.ApacheTest):
@mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_autohsts_increase(self, _mock_restart):
@mock.patch("certbot_apache.configurator.ApacheConfigurator.prepare")
def test_autohsts_increase(self, mock_prepare, _mock_restart):
self.config._prepared = False
maxage = "\"max-age={0}\""
initial_val = maxage.format(constants.AUTOHSTS_STEPS[0])
inc_val = maxage.format(constants.AUTOHSTS_STEPS[1])
@@ -69,6 +71,7 @@ class AutoHSTSTest(util.ApacheTest):
# Verify increased value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
inc_val)
self.assertTrue(mock_prepare.called)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.configurator.ApacheConfigurator._autohsts_increase")

View File

@@ -43,6 +43,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -46,6 +46,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -43,6 +43,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -47,6 +47,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -42,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -36,6 +36,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -43,6 +43,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -40,6 +40,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Communications :: Email :: Mail Transport Agents',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -3,6 +3,7 @@
from __future__ import print_function
import argparse
import copy
import functools
import glob
import logging
import logging.handlers
@@ -120,7 +121,7 @@ More detailed help:
# These argparse parameters should be removed when detecting defaults.
ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",)
ARGPARSE_PARAMS_TO_REMOVE = ("const", "type",)
# These sets are used when to help detect options set by the user.
@@ -263,13 +264,23 @@ def option_was_set(option, value):
return set_by_cli(option) or not has_default_value(option, value)
def argparse_list(cast, values):
"""Wrap an argparse type function in a list (default: str)"""
if cast is None:
cast = str
return [cast(value) for value in values]
def argparse_type(variable):
"""Return our argparse type function for a config variable (default: str)"""
# pylint: disable=protected-access
if helpful_parser is not None:
for action in helpful_parser.parser._actions:
if action.type is not None and action.dest == variable:
return action.type
if action.dest == variable:
if action.nargs in ['N', '*', '+', argparse.REMAINDER]:
return functools.partial(argparse_list, action.type)
if action.type is not None:
return action.type
return str
def read_file(filename, mode="rb"):

View File

@@ -620,6 +620,9 @@ class GenericUpdater(object):
methods, and interfaces.GenericUpdater.register(InstallerClass) should
be called from the installer code.
The plugins implementing this enhancement are responsible of handling
the saving of configuration checkpoints as well as other calls to
interface methods of `interfaces.IInstaller` such as prepare() and restart()
"""
@abc.abstractmethod

View File

@@ -1199,11 +1199,11 @@ def renew_cert(config, plugins, lineage):
# In case of a renewal, reload server to pick up new certificate.
# In principle we could have a configuration option to inhibit this
# from happening.
# Run deployer
updater.run_renewal_deployer(config, renewed_lineage, installer)
installer.restart()
notify("new certificate deployed with reload of {0} server; fullchain is {1}".format(
config.installer, lineage.fullchain), pause=False)
# Run deployer
def certonly(config, plugins):
"""Authenticate & obtain cert, but do not install it.

View File

@@ -88,7 +88,8 @@ class AutoHSTSEnhancement(object):
The plugins implementing new style enhancements are responsible of handling
the saving of configuration checkpoints as well as calling possible restarts
of managed software themselves.
of managed software themselves. For update_autohsts method, the installer may
have to call prepare() to finalize the plugin initialization.
Methods:
enable_autohsts is called when the header is initially installed using a
@@ -112,6 +113,10 @@ class AutoHSTSEnhancement(object):
:param lineage: Certificate lineage object
:type lineage: certbot.storage.RenewableCert
.. note:: prepare() method inherited from `interfaces.IPlugin` might need
to be called manually within implementation of this interface method
to finalize the plugin initialization.
"""
@abc.abstractmethod

View File

@@ -39,6 +39,35 @@ def pick_authenticator(
return pick_plugin(
config, default, plugins, question, (interfaces.IAuthenticator,))
def get_unprepared_installer(config, plugins):
"""
Get an unprepared interfaces.IInstaller object.
:param certbot.interfaces.IConfig config: Configuration
:param certbot.plugins.disco.PluginsRegistry plugins:
All plugins registered as entry points.
:returns: Unprepared installer plugin or None
:rtype: IPlugin or None
"""
_, req_inst = cli_plugin_requests(config)
if not req_inst:
return None
installers = plugins.filter(lambda p_ep: p_ep.name == req_inst)
installers.init(config)
installers = installers.verify((interfaces.IInstaller,))
if len(installers) > 1:
raise errors.PluginSelectionError(
"Found multiple installers with the name %s, Certbot is unable to "
"determine which one to use. Skipping." % req_inst)
if installers:
inst = list(installers.values())[0]
logger.debug("Selecting plugin: %s", inst)
return inst.init(config)
else:
raise errors.PluginSelectionError(
"Could not select or initialize the requested installer %s." % req_inst)
def pick_plugin(config, default, plugins, question, ifaces):
"""Pick plugin.

View File

@@ -6,10 +6,13 @@ import unittest
import mock
import zope.component
from certbot import errors
from certbot import interfaces
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot.display import util as display_util
from certbot.plugins.disco import PluginsRegistry
from certbot.tests import util as test_util
from certbot import interfaces
class ConveniencePickPluginTest(unittest.TestCase):
@@ -170,5 +173,48 @@ class ChoosePluginTest(unittest.TestCase):
self.assertTrue("default" in mock_util().menu.call_args[1])
class GetUnpreparedInstallerTest(test_util.ConfigTestCase):
"""Tests for certbot.plugins.selection.get_unprepared_installer."""
def setUp(self):
super(GetUnpreparedInstallerTest, self).setUp()
self.mock_apache_fail_ep = mock.Mock(
description_with_name="afail")
self.mock_apache_fail_ep.name = "afail"
self.mock_apache_ep = mock.Mock(
description_with_name="apache")
self.mock_apache_ep.name = "apache"
self.mock_apache_plugin = mock.MagicMock()
self.mock_apache_ep.init.return_value = self.mock_apache_plugin
self.plugins = PluginsRegistry({
"afail": self.mock_apache_fail_ep,
"apache": self.mock_apache_ep,
})
def _call(self):
from certbot.plugins.selection import get_unprepared_installer
return get_unprepared_installer(self.config, self.plugins)
def test_no_installer_defined(self):
self.config.configurator = None
self.assertEquals(self._call(), None)
def test_no_available_installers(self):
self.config.configurator = "apache"
self.plugins = PluginsRegistry({})
self.assertRaises(errors.PluginSelectionError, self._call)
def test_get_plugin(self):
self.config.configurator = "apache"
installer = self._call()
self.assertTrue(installer is self.mock_apache_plugin)
def test_multiple_installers_returned(self):
self.config.configurator = "apache"
# Two plugins with the same name
self.mock_apache_fail_ep.name = "apache"
self.assertRaises(errors.PluginSelectionError, self._call)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -1493,17 +1493,17 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met
self.assertTrue(mock_handle.called)
@mock.patch('certbot.plugins.selection.choose_configurator_plugins')
def test_plugin_selection_error(self, mock_choose):
@mock.patch('certbot.updater._run_updaters')
def test_plugin_selection_error(self, mock_run, mock_choose):
mock_choose.side_effect = errors.PluginSelectionError
self.assertRaises(errors.PluginSelectionError, main.renew_cert,
None, None, None)
with mock.patch('certbot.updater.logger.warning') as mock_log:
self.config.dry_run = False
updater.run_generic_updaters(self.config, None, None)
self.assertTrue(mock_log.called)
self.assertTrue("Could not choose appropriate plugin for updaters"
in mock_log.call_args[0][0])
self.config.dry_run = False
updater.run_generic_updaters(self.config, None, None)
# Make sure we're returning None, and hence not trying to run the
# without installer
self.assertFalse(mock_run.called)
class UnregisterTest(unittest.TestCase):

View File

@@ -23,13 +23,15 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
@mock.patch('certbot.main._get_and_save_cert')
@mock.patch('certbot.plugins.selection.choose_configurator_plugins')
@mock.patch('certbot.plugins.selection.get_unprepared_installer')
@test_util.patch_get_utility()
def test_server_updates(self, _, mock_select, mock_getsave):
def test_server_updates(self, _, mock_geti, mock_select, mock_getsave):
mock_getsave.return_value = mock.MagicMock()
mock_generic_updater = self.generic_updater
# Generic Updater
mock_select.return_value = (mock_generic_updater, None)
mock_geti.return_value = mock_generic_updater
with mock.patch('certbot.main._init_le_client'):
main.renew_cert(self.config, None, mock.MagicMock())
self.assertTrue(mock_generic_updater.restart.called)
@@ -62,9 +64,9 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
self.assertEquals(mock_log.call_args[0][0],
"Skipping renewal deployer in dry-run mode.")
@mock.patch('certbot.plugins.selection.choose_configurator_plugins')
def test_enhancement_updates(self, mock_select):
mock_select.return_value = (self.mockinstaller, None)
@mock.patch('certbot.plugins.selection.get_unprepared_installer')
def test_enhancement_updates(self, mock_geti):
mock_geti.return_value = self.mockinstaller
updater.run_generic_updaters(self.config, mock.MagicMock(), None)
self.assertTrue(self.mockinstaller.update_autohsts.called)
self.assertEqual(self.mockinstaller.update_autohsts.call_count, 1)
@@ -74,10 +76,10 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
self.mockinstaller)
self.assertTrue(self.mockinstaller.deploy_autohsts.called)
@mock.patch('certbot.plugins.selection.choose_configurator_plugins')
def test_enhancement_updates_not_called(self, mock_select):
@mock.patch('certbot.plugins.selection.get_unprepared_installer')
def test_enhancement_updates_not_called(self, mock_geti):
self.config.disable_renew_updates = True
mock_select.return_value = (self.mockinstaller, None)
mock_geti.return_value = self.mockinstaller
updater.run_generic_updaters(self.config, mock.MagicMock(), None)
self.assertFalse(self.mockinstaller.update_autohsts.called)
@@ -87,8 +89,8 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
self.mockinstaller)
self.assertFalse(self.mockinstaller.deploy_autohsts.called)
@mock.patch('certbot.plugins.selection.choose_configurator_plugins')
def test_enhancement_no_updater(self, mock_select):
@mock.patch('certbot.plugins.selection.get_unprepared_installer')
def test_enhancement_no_updater(self, mock_geti):
FAKEINDEX = [
{
"name": "Test",
@@ -98,7 +100,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase):
"enable_function": "enable_autohsts"
}
]
mock_select.return_value = (self.mockinstaller, None)
mock_geti.return_value = self.mockinstaller
with mock.patch("certbot.plugins.enhancements._INDEX", FAKEINDEX):
updater.run_generic_updaters(self.config, mock.MagicMock(), None)
self.assertFalse(self.mockinstaller.update_autohsts.called)

View File

@@ -28,13 +28,13 @@ def run_generic_updaters(config, lineage, plugins):
logger.debug("Skipping updaters in dry-run mode.")
return
try:
# installers are used in auth mode to determine domain names
installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "certonly")
except errors.PluginSelectionError as e:
installer = plug_sel.get_unprepared_installer(config, plugins)
except errors.Error as e:
logger.warning("Could not choose appropriate plugin for updaters: %s", e)
return
_run_updaters(lineage, installer, config)
_run_enhancement_updaters(lineage, installer, config)
if installer:
_run_updaters(lineage, installer, config)
_run_enhancement_updaters(lineage, installer, config)
def run_renewal_deployer(config, lineage, installer):
"""Helper function to run deployer interface method if supported by the used

View File

@@ -1092,9 +1092,9 @@ idna==2.5 \
ipaddress==1.0.16 \
--hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \
--hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0
josepy==1.0.1 \
--hash=sha256:354a3513038a38bbcd27c97b7c68a8f3dfaff0a135b20a92c6db4cc4ea72915e \
--hash=sha256:9f48b88ca37f0244238b1cc77723989f7c54f7b90b2eee6294390bacfe870acc
josepy==1.1.0 \
--hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \
--hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086
linecache2==1.0.0 \
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c

View File

@@ -96,9 +96,9 @@ idna==2.5 \
ipaddress==1.0.16 \
--hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \
--hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0
josepy==1.0.1 \
--hash=sha256:354a3513038a38bbcd27c97b7c68a8f3dfaff0a135b20a92c6db4cc4ea72915e \
--hash=sha256:9f48b88ca37f0244238b1cc77723989f7c54f7b90b2eee6294390bacfe870acc
josepy==1.1.0 \
--hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \
--hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086
linecache2==1.0.0 \
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c

View File

@@ -35,6 +35,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -99,6 +99,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -26,7 +26,7 @@ ipython==5.5.0
ipython-genutils==0.2.0
Jinja2==2.9.6
jmespath==0.9.3
josepy==1.0.1
josepy==1.1.0
logger==1.4
logilab-common==1.4.1
MarkupSafe==1.0
@@ -51,7 +51,7 @@ pytest-forked==0.2
pytest-xdist==1.20.1
python-dateutil==2.6.1
python-digitalocean==1.11
PyYAML==3.12
PyYAML==3.13
repoze.sphinx.autointerface==0.8
requests-file==1.4.2
requests-toolbelt==0.8.0

22
tox.ini
View File

@@ -14,7 +14,8 @@ pip_install = {toxinidir}/tools/pip_install_editable.sh
# before the script moves on to the next package. All dependencies are pinned
# to a specific version for increased stability for developers.
install_and_test = {toxinidir}/tools/install_and_test.sh
python37_compatible_dns_packages =
dns_packages =
certbot-dns-cloudflare \
certbot-dns-cloudxns \
certbot-dns-digitalocean \
certbot-dns-dnsimple \
@@ -24,22 +25,14 @@ python37_compatible_dns_packages =
certbot-dns-nsone \
certbot-dns-rfc2136 \
certbot-dns-route53
dns_packages =
certbot-dns-cloudflare \
{[base]python37_compatible_dns_packages}
nondns_packages =
all_packages =
acme[dev] \
.[dev] \
certbot-apache \
{[base]dns_packages} \
certbot-nginx \
certbot-postfix \
letshelp-certbot
python37_compatible_packages =
{[base]nondns_packages} \
{[base]python37_compatible_dns_packages}
all_packages =
{[base]nondns_packages} \
{[base]dns_packages}
install_packages =
{toxinidir}/tools/pip_install_editable.sh {[base]all_packages}
source_paths =
@@ -69,13 +62,6 @@ commands =
setenv =
PYTHONHASHSEED = 0
[testenv:py37]
commands =
{[base]install_and_test} {[base]python37_compatible_packages}
python tests/lock_test.py
setenv =
{[testenv]setenv}
[testenv:py27-oldest]
commands =
{[testenv]commands}