Merge remote-tracking branch 'letsencrypt/master'

This commit is contained in:
TheNavigat 2016-02-14 21:24:26 +02:00
commit 5996318e40
17 changed files with 184 additions and 138 deletions

View file

@ -58,7 +58,7 @@ RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \
-e /opt/letsencrypt/src/letsencrypt-nginx \
-e /opt/letsencrypt/src/letshelp-letsencrypt \
-e /opt/letsencrypt/src/letsencrypt-compatibility-test \
-e /opt/letsencrypt/src[dev,docs,testing]
-e /opt/letsencrypt/src[dev,docs]
# install in editable mode (-e) to save space: it's not possible to
# "rm -rf /opt/letsencrypt/src" (it's stays in the underlaying image);

View file

@ -51,11 +51,11 @@ client will guide you through the process of obtaining and installing certs
interactively.
You can also tell it exactly what you want it to do from the command line.
For instance, if you want to obtain a cert for ``thing.com``,
``www.thing.com``, and ``otherthing.net``, using the Apache plugin to both
For instance, if you want to obtain a cert for ``example.com``,
``www.example.com``, and ``other.example.net``, using the Apache plugin to both
obtain and install the certs, you could do this::
./letsencrypt-auto --apache -d thing.com -d www.thing.com -d otherthing.net
./letsencrypt-auto --apache -d example.com -d www.example.com -d other.example.net
(The first time you run the command, it will make an account, and ask for an
email and agreement to the Let's Encrypt Subscriber Agreement; you can
@ -64,7 +64,7 @@ automate those with ``--email`` and ``--agree-tos``)
If you want to use a webserver that doesn't have full plugin support yet, you
can still use "standalone" or "webroot" plugins to obtain a certificate::
./letsencrypt-auto certonly --standalone --email admin@thing.com -d thing.com -d www.thing.com -d otherthing.net
./letsencrypt-auto certonly --standalone --email admin@example.com -d example.com -d www.example.com -d other.example.net
Understanding the client in more depth

View file

@ -180,40 +180,41 @@ class Client(object): # pylint: disable=too-many-instance-attributes
raise errors.UnexpectedUpdate(authzr)
return authzr
def request_challenges(self, identifier, new_authzr_uri):
def request_challenges(self, identifier, new_authzr_uri=None):
"""Request challenges.
:param identifier: Identifier to be challenged.
:type identifier: `.messages.Identifier`
:param str new_authzr_uri: new-authorization URI
:param .messages.Identifier identifier: Identifier to be challenged.
:param str new_authzr_uri: ``new-authorization`` URI. If omitted,
will default to value found in ``directory``.
:returns: Authorization Resource.
:rtype: `.AuthorizationResource`
"""
new_authz = messages.NewAuthorization(identifier=identifier)
response = self.net.post(new_authzr_uri, new_authz)
response = self.net.post(self.directory.new_authz
if new_authzr_uri is None else new_authzr_uri,
new_authz)
# TODO: handle errors
assert response.status_code == http_client.CREATED
return self._authzr_from_response(response, identifier)
def request_domain_challenges(self, domain, new_authz_uri):
def request_domain_challenges(self, domain, new_authzr_uri=None):
"""Request challenges for domain names.
This is simply a convenience function that wraps around
`request_challenges`, but works with domain names instead of
generic identifiers.
generic identifiers. See ``request_challenges`` for more
documentation.
:param str domain: Domain name to be challenged.
:param str new_authzr_uri: new-authorization URI
:returns: Authorization Resource.
:rtype: `.AuthorizationResource`
"""
return self.request_challenges(messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value=domain), new_authz_uri)
typ=messages.IDENTIFIER_FQDN, value=domain), new_authzr_uri)
def answer_challenge(self, challb, response):
"""Answer challenge.

View file

@ -38,6 +38,8 @@ class ClientTest(unittest.TestCase):
'https://www.letsencrypt-demo.org/acme/new-reg',
messages.Revocation:
'https://www.letsencrypt-demo.org/acme/revoke-cert',
messages.NewAuthorization:
'https://www.letsencrypt-demo.org/acme/new-authz',
})
from acme.client import Client
@ -142,7 +144,7 @@ class ClientTest(unittest.TestCase):
regr = self.client.update_registration.call_args[0][0]
self.assertEqual(self.regr.terms_of_service, regr.body.agreement)
def test_request_challenges(self):
def _prepare_response_for_request_challenges(self):
self.response.status_code = http_client.CREATED
self.response.headers['Location'] = self.authzr.uri
self.response.json.return_value = self.authz.to_json()
@ -150,10 +152,20 @@ class ClientTest(unittest.TestCase):
'next': {'url': self.authzr.new_cert_uri},
}
self.client.request_challenges(self.identifier, self.authzr.uri)
# TODO: test POST call arguments
def test_request_challenges(self):
self._prepare_response_for_request_challenges()
self.client.request_challenges(self.identifier)
self.net.post.assert_called_once_with(
self.directory.new_authz,
messages.NewAuthorization(identifier=self.identifier))
# TODO: split here and separate test
def test_requets_challenges_custom_uri(self):
self._prepare_response_for_request_challenges()
self.client.request_challenges(self.identifier, 'URI')
self.net.post.assert_called_once_with('URI', mock.ANY)
def test_request_challenges_unexpected_update(self):
self._prepare_response_for_request_challenges()
self.response.json.return_value = self.authz.update(
identifier=self.identifier.update(value='foo')).to_json()
self.assertRaises(
@ -162,15 +174,20 @@ class ClientTest(unittest.TestCase):
def test_request_challenges_missing_next(self):
self.response.status_code = http_client.CREATED
self.assertRaises(
errors.ClientError, self.client.request_challenges,
self.identifier, self.regr)
self.assertRaises(errors.ClientError, self.client.request_challenges,
self.identifier)
def test_request_domain_challenges(self):
self.client.request_challenges = mock.MagicMock()
self.assertEqual(
self.client.request_challenges(self.identifier),
self.client.request_domain_challenges('example.com', self.regr))
self.client.request_domain_challenges('example.com'))
def test_request_domain_challenges_custom_uri(self):
self.client.request_challenges = mock.MagicMock()
self.assertEqual(
self.client.request_challenges(self.identifier, 'URI'),
self.client.request_domain_challenges('example.com', 'URI'))
def test_answer_challenge(self):
self.response.links['up'] = {'url': self.challr.authzr_uri}

View file

@ -34,17 +34,18 @@ if sys.version_info < (2, 7):
else:
install_requires.append('mock')
dev_extras = [
'nose',
'pep8',
'tox',
]
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',
'sphinxcontrib-programoutput',
]
testing_extras = [
'nose',
'tox',
]
setup(
name='acme',
@ -74,8 +75,8 @@ setup(
include_package_data=True,
install_requires=install_requires,
extras_require={
'dev': dev_extras,
'docs': docs_extras,
'testing': testing_extras,
},
entry_points={
'console_scripts': [

View file

@ -111,11 +111,11 @@ potentially be a separate directory for each domain. When requested a
certificate for multiple domains, each domain will use the most recently
specified ``--webroot-path``. So, for instance,
``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/eg -d eg.is -d www.eg.is``
``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net``
would obtain a single certificate for all of those names, using the
``/var/www/example`` webroot directory for the first two, and
``/var/www/eg`` for the second two.
``/var/www/other`` for the second two.
The webroot plugin works by creating a temporary file for each of your requested
domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's
@ -130,7 +130,7 @@ made to your web server would look like:
Note that to use the webroot plugin, your server must be configured to serve
files from hidden directories. If ``/.well-known`` is treated specially by
your webserver configuration, you might need to modify the configuration
to ensure that files inside ``/.well-known/ache-challenge`` are served by
to ensure that files inside ``/.well-known/acme-challenge`` are served by
the webserver.
Standalone
@ -439,8 +439,8 @@ want to use the Apache plugin, it has to be installed separately:
emerge -av app-crypt/letsencrypt
emerge -av app-crypt/letsencrypt-apache
Currently, only the Apache plugin is included in Portage. However, if you
want the nginx plugin, you can use Layman to add the mrueg overlay which
Currently, only the Apache plugin is included in Portage. However, if you
want the nginx plugin, you can use Layman to add the mrueg overlay which
does include the nginx plugin package:
.. code-block:: shell
@ -450,9 +450,9 @@ does include the nginx plugin package:
layman -a mrueg
emerge -av app-crypt/letsencrypt-nginx
When using the Apache plugin, you will run into a "cannot find a cert or key
When using the Apache plugin, you will run into a "cannot find a cert or key
directive" error if you're sporting the default Gentoo ``httpd.conf``.
You can fix this by commenting out two lines in ``/etc/apache2/httpd.conf``
You can fix this by commenting out two lines in ``/etc/apache2/httpd.conf``
as follows:
Change
@ -471,7 +471,7 @@ to
LoadModule ssl_module modules/mod_ssl.so
#</IfDefine>
For the time being, this is the only way for the Apache plugin to recognise
For the time being, this is the only way for the Apache plugin to recognise
the appropriate directives when installing the certificate.
Note: this change is not required for the other plugins.

View file

@ -18,25 +18,31 @@ set -e # Work even if somebody does "sh thisscript.sh".
XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN=${VENV_PATH}/bin
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.5.0.dev0"
# This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
for arg in "$@" ; do
# This first clause is redundant with the third, but hedging on portability
if [ "$arg" = "-v" ] || [ "$arg" = "--verbose" ] || echo "$arg" | grep -E -- "-v+$" ; then
VERBOSE=1
elif [ "$arg" = "--no-self-upgrade" ] ; then
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1
elif [ "$arg" = "--os-packages-only" ] ; then
OS_PACKAGES_ONLY=1
elif [ "$arg" = "--debug" ]; then
DEBUG=1
fi
case "$arg" in
--debug)
DEBUG=1;;
--os-packages-only)
OS_PACKAGES_ONLY=1;;
--no-self-upgrade)
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1;;
--verbose)
VERBOSE=1;;
[!-]*|-*[!v]*|-)
# Anything that isn't -v, -vv, etc.: that is, anything that does not
# start with a -, contains anything that's not a v, or is just "-"
;;
*) # -v+ remains.
VERBOSE=1;;
esac
done
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
@ -91,21 +97,18 @@ ExperimentalBootstrap() {
}
DeterminePythonVersion() {
if command -v python2.7 > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python2.7}
elif command -v python27 > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python27}
elif command -v python2 > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python2}
elif command -v python > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python}
else
echo "Cannot find any Pythons... please install one!"
for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do
# Break (while keeping the LE_PYTHON value) if found.
command -v "$LE_PYTHON" > /dev/null && break
done
if [ "$?" != "0" ]; then
echo "Cannot find any Pythons; please install one!"
exit 1
fi
export LE_PYTHON
PYVER=`"$LE_PYTHON" --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
if [ $PYVER -lt 26 ]; then
PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
if [ "$PYVER" -lt 26 ]; then
echo "You have an ancient version of Python entombed in your operating system..."
echo "This isn't going to work; you'll need at least version 2.6."
exit 1
@ -324,13 +327,13 @@ BootstrapGentooCommon() {
case "$PACKAGE_MANAGER" in
(paludis)
"$SUDO" cave resolve --keep-targets if-possible $PACKAGES -x
"$SUDO" cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
;;
(pkgcore)
"$SUDO" pmerge --noreplace $PACKAGES
"$SUDO" pmerge --noreplace --oneshot $PACKAGES
;;
(portage|*)
"$SUDO" emerge --noreplace $PACKAGES
"$SUDO" emerge --noreplace --oneshot $PACKAGES
;;
esac
}
@ -345,20 +348,27 @@ BootstrapFreeBsd() {
BootstrapMac() {
if ! hash brew 2>/dev/null; then
echo "Homebrew Not Installed\nDownloading..."
echo "Homebrew not installed.\nDownloading..."
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi
brew install augeas
brew install dialog
if [ -z "$(brew list --versions augeas)" ]; then
echo "augeas not installed.\nInstalling augeas from Homebrew..."
brew install augeas
fi
if [ -z "$(brew list --versions dialog)" ]; then
echo "dialog not installed.\nInstalling dialog from Homebrew..."
brew install dialog
fi
if ! hash pip 2>/dev/null; then
echo "pip Not Installed\nInstalling python from Homebrew..."
echo "pip not installed.\nInstalling python from Homebrew..."
brew install python
fi
if ! hash virtualenv 2>/dev/null; then
echo "virtualenv Not Installed\nInstalling with pip"
echo "virtualenv not installed.\nInstalling with pip..."
pip install virtualenv
fi
}

View file

@ -18,25 +18,31 @@ set -e # Work even if somebody does "sh thisscript.sh".
XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN=${VENV_PATH}/bin
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}"
# This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
for arg in "$@" ; do
# This first clause is redundant with the third, but hedging on portability
if [ "$arg" = "-v" ] || [ "$arg" = "--verbose" ] || echo "$arg" | grep -E -- "-v+$" ; then
VERBOSE=1
elif [ "$arg" = "--no-self-upgrade" ] ; then
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1
elif [ "$arg" = "--os-packages-only" ] ; then
OS_PACKAGES_ONLY=1
elif [ "$arg" = "--debug" ]; then
DEBUG=1
fi
case "$arg" in
--debug)
DEBUG=1;;
--os-packages-only)
OS_PACKAGES_ONLY=1;;
--no-self-upgrade)
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1;;
--verbose)
VERBOSE=1;;
[!-]*|-*[!v]*|-)
# Anything that isn't -v, -vv, etc.: that is, anything that does not
# start with a -, contains anything that's not a v, or is just "-"
;;
*) # -v+ remains.
VERBOSE=1;;
esac
done
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
@ -91,21 +97,18 @@ ExperimentalBootstrap() {
}
DeterminePythonVersion() {
if command -v python2.7 > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python2.7}
elif command -v python27 > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python27}
elif command -v python2 > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python2}
elif command -v python > /dev/null ; then
export LE_PYTHON=${LE_PYTHON:-python}
else
echo "Cannot find any Pythons... please install one!"
for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do
# Break (while keeping the LE_PYTHON value) if found.
command -v "$LE_PYTHON" > /dev/null && break
done
if [ "$?" != "0" ]; then
echo "Cannot find any Pythons; please install one!"
exit 1
fi
export LE_PYTHON
PYVER=`"$LE_PYTHON" --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
if [ $PYVER -lt 26 ]; then
PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
if [ "$PYVER" -lt 26 ]; then
echo "You have an ancient version of Python entombed in your operating system..."
echo "This isn't going to work; you'll need at least version 2.6."
exit 1

View file

@ -1,19 +1,26 @@
BootstrapMac() {
if ! hash brew 2>/dev/null; then
echo "Homebrew Not Installed\nDownloading..."
echo "Homebrew not installed.\nDownloading..."
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi
brew install augeas
brew install dialog
if [ -z "$(brew list --versions augeas)" ]; then
echo "augeas not installed.\nInstalling augeas from Homebrew..."
brew install augeas
fi
if [ -z "$(brew list --versions dialog)" ]; then
echo "dialog not installed.\nInstalling dialog from Homebrew..."
brew install dialog
fi
if ! hash pip 2>/dev/null; then
echo "pip Not Installed\nInstalling python from Homebrew..."
echo "pip not installed.\nInstalling python from Homebrew..."
brew install python
fi
if ! hash virtualenv 2>/dev/null; then
echo "virtualenv Not Installed\nInstalling with pip"
echo "virtualenv not installed.\nInstalling with pip..."
pip install virtualenv
fi
}

View file

@ -319,12 +319,11 @@ def _handle_identical_cert_request(config, cert):
elif config.verb == "certonly":
keep_opt = "Keep the existing certificate for now"
choices = [keep_opt,
"Renew & replace the cert (limit ~5 per 7 days)",
"Cancel this operation and do nothing"]
"Renew & replace the cert (limit ~5 per 7 days)"]
display = zope.component.getUtility(interfaces.IDisplay)
response = display.menu(question, choices, "OK", "Cancel", default=0)
if response[0] == "cancel" or response[1] == 2:
if response[0] == display_util.CANCEL:
# TODO: Add notification related to command-line options for
# skipping the menu for this case.
raise errors.Error(

View file

@ -112,6 +112,21 @@ def update_configuration(lineagename, target, cli_config):
return configobj.ConfigObj(config_filename)
def get_link_target(link):
"""Get an absolute path to the target of link.
:param str link: Path to a symbolic link
:returns: Absolute path to the target of link
:rtype: str
"""
target = os.readlink(link)
if not os.path.isabs(target):
target = os.path.join(os.path.dirname(link), target)
return os.path.abspath(target)
class RenewableCert(object): # pylint: disable=too-many-instance-attributes
"""Renewable certificate.
@ -194,13 +209,15 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
def _check_symlinks(self):
"""Raises an exception if a symlink doesn't exist"""
def check(link):
"""Checks if symlink points to a file that exists"""
return os.path.exists(os.path.realpath(link))
for kind in ALL_FOUR:
if not check(getattr(self, kind)):
link = getattr(self, kind)
if not os.path.islink(link):
raise errors.CertStorageError(
"link: {0} does not exist".format(getattr(self, kind)))
"expected {0} to be a symlink".format(link))
target = get_link_target(link)
if not os.path.exists(target):
raise errors.CertStorageError("target {0} of symlink {1} does "
"not exist".format(target, link))
def _consistent(self):
"""Are the files associated with this lineage self-consistent?
@ -225,10 +242,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
return False
for kind in ALL_FOUR:
link = getattr(self, kind)
where = os.path.dirname(link)
target = os.readlink(link)
if not os.path.isabs(target):
target = os.path.join(where, target)
target = get_link_target(link)
# Each element's link must point within the cert lineage's
# directory within the official archive directory
@ -343,10 +357,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
logger.debug("Expected symlink %s for %s does not exist.",
link, kind)
return None
target = os.readlink(link)
if not os.path.isabs(target):
target = os.path.join(os.path.dirname(link), target)
return os.path.abspath(target)
return get_link_target(link)
def current_version(self, kind):
"""Returns numerical version of the specified item.

View file

@ -687,6 +687,10 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertRaises(errors.CertStorageError,
storage.RenewableCert,
self.config.filename, self.cli_config)
os.symlink("missing", self.config[ALL_FOUR[0]])
self.assertRaises(errors.CertStorageError,
storage.RenewableCert,
self.config.filename, self.cli_config)
if __name__ == "__main__":

View file

@ -1,9 +1,6 @@
[easy_install]
zip_ok = false
[aliases]
dev = develop easy_install letsencrypt[dev,docs,testing]
[nosetests]
nocapture=1
cover-package=letsencrypt,acme,letsencrypt_apache,letsencrypt_nginx

View file

@ -65,7 +65,12 @@ else:
dev_extras = [
# Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289
'astroid==1.3.5',
'coverage',
'nose',
'nosexcover',
'pep8',
'pylint==1.4.2', # upstream #248
'tox',
'twine',
'wheel',
]
@ -77,14 +82,6 @@ docs_extras = [
'sphinxcontrib-programoutput',
]
testing_extras = [
'coverage',
'nose',
'nosexcover',
'pep8',
'tox',
]
setup(
name='letsencrypt',
version=version,
@ -120,7 +117,6 @@ setup(
extras_require={
'dev': dev_extras,
'docs': docs_extras,
'testing': testing_extras,
},
# to test all packages run "python setup.py test -s

View file

@ -4,8 +4,8 @@
export VENV_ARGS="--python python2"
./tools/_venv_common.sh \
-e acme[testing] \
-e .[dev,docs,testing] \
-e acme[dev] \
-e .[dev,docs] \
-e letsencrypt-apache \
-e letsencrypt-nginx \
-e letshelp-letsencrypt \

View file

@ -5,4 +5,4 @@ export VENV_NAME="${VENV_NAME:-venv3}"
export VENV_ARGS="--python python3"
./tools/_venv_common.sh \
-e acme[testing] \
-e acme[dev] \

14
tox.ini
View file

@ -15,9 +15,9 @@ envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint
# packages installed separately to ensure that downstream deps problems
# are detected, c.f. #1002
commands =
pip install -e acme[testing]
pip install -e acme[dev]
nosetests -v acme
pip install -e .[testing]
pip install -e .[dev]
nosetests -v letsencrypt
pip install -e letsencrypt-apache
nosetests -v letsencrypt_apache
@ -40,23 +40,23 @@ deps =
[testenv:py33]
commands =
pip install -e acme[testing]
pip install -e acme[dev]
nosetests -v acme
[testenv:py34]
commands =
pip install -e acme[testing]
pip install -e acme[dev]
nosetests -v acme
[testenv:py35]
commands =
pip install -e acme[testing]
pip install -e acme[dev]
nosetests -v acme
[testenv:cover]
basepython = python2.7
commands =
pip install -e acme -e .[testing] -e letsencrypt-apache -e letsencrypt-nginx -e letshelp-letsencrypt
pip install -e acme[dev] -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letshelp-letsencrypt
./tox.cover.sh
[testenv:lint]
@ -66,7 +66,7 @@ basepython = python2.7
# duplicate code checking; if one of the commands fails, others will
# continue, but tox return code will reflect previous error
commands =
pip install -e acme -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt
pip install -e acme[dev] -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt
./pep8.travis.sh
pylint --rcfile=.pylintrc letsencrypt
pylint --rcfile=acme/.pylintrc acme/acme