Merge remote-tracking branch 'origin/master' into subargs

This commit is contained in:
Peter Eckersley
2015-09-29 14:49:32 -07:00
26 changed files with 229 additions and 75 deletions
+2 -2
View File
@@ -62,5 +62,5 @@ RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \
# bash" and investigate, apply patches, etc.
ENV PATH /opt/letsencrypt/venv/bin:$PATH
# TODO: is --text really necessary?
ENTRYPOINT [ "letsencrypt", "--text" ]
ENTRYPOINT [ "letsencrypt" ]
+35 -1
View File
@@ -25,6 +25,14 @@ class Challenge(jose.TypedJSONObjectWithFields):
"""ACME challenge."""
TYPES = {}
@classmethod
def from_json(cls, jobj):
try:
return super(Challenge, cls).from_json(jobj)
except jose.UnrecognizedTypeError as error:
logger.debug(error)
return UnrecognizedChallenge.from_json(jobj)
class ContinuityChallenge(Challenge): # pylint: disable=abstract-method
"""Client validation challenges."""
@@ -42,6 +50,32 @@ class ChallengeResponse(jose.TypedJSONObjectWithFields):
resource = fields.Resource(resource_type)
class UnrecognizedChallenge(Challenge):
"""Unrecognized challenge.
ACME specification defines a generic framework for challenges and
defines some standard challenges that are implemented in this
module. However, other implementations (including peers) might
define additional challenge types, which should be ignored if
unrecognized.
:ivar jobj: Original JSON decoded object.
"""
def __init__(self, jobj):
super(UnrecognizedChallenge, self).__init__()
object.__setattr__(self, "jobj", jobj)
def to_partial_json(self):
# pylint: disable=no-member
return self.jobj
@classmethod
def from_json(cls, jobj):
return cls(jobj)
@Challenge.register
class SimpleHTTP(DVChallenge):
"""ACME "simpleHttp" challenge.
@@ -542,7 +576,7 @@ class DNS(DVChallenge):
def check_validation(self, validation, account_public_key):
"""Check validation.
:param validation
:param JWS validation:
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or
+26
View File
@@ -17,6 +17,32 @@ CERT = test_util.load_cert('cert.pem')
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
class ChallengeTest(unittest.TestCase):
def test_from_json_unrecognized(self):
from acme.challenges import Challenge
from acme.challenges import UnrecognizedChallenge
chall = UnrecognizedChallenge({"type": "foo"})
# pylint: disable=no-member
self.assertEqual(chall, Challenge.from_json(chall.jobj))
class UnrecognizedChallengeTest(unittest.TestCase):
def setUp(self):
from acme.challenges import UnrecognizedChallenge
self.jobj = {"type": "foo"}
self.chall = UnrecognizedChallenge(self.jobj)
def test_to_partial_json(self):
self.assertEqual(self.jobj, self.chall.to_partial_json())
def test_from_json(self):
from acme.challenges import UnrecognizedChallenge
self.assertEqual(
self.chall, UnrecognizedChallenge.from_json(self.jobj))
class SimpleHTTPTest(unittest.TestCase):
def setUp(self):
+1
View File
@@ -274,6 +274,7 @@ class AuthorizationTest(unittest.TestCase):
def setUp(self):
from acme.messages import ChallengeBody
from acme.messages import STATUS_VALID
self.challbs = (
ChallengeBody(
uri='http://challb1', status=STATUS_VALID,
-6
View File
@@ -21,9 +21,3 @@
.. automodule:: letsencrypt.display.enhancements
:members:
:mod:`letsencrypt.display.revocation`
=====================================
.. automodule:: letsencrypt.display.revocation
:members:
-5
View File
@@ -1,5 +0,0 @@
:mod:`letsencrypt.recovery_token`
--------------------------------------------------
.. automodule:: letsencrypt.recovery_token
:members:
-5
View File
@@ -1,5 +0,0 @@
:mod:`letsencrypt.revoker`
--------------------------
.. automodule:: letsencrypt.revoker
:members:
@@ -1162,7 +1162,7 @@ def _get_mod_deps(mod_name):
changes.
.. warning:: If all deps are not included, it may cause incorrect parsing
behavior, due to enable_mod's shortcut for updating the parser's
currently defined modules (:method:`.ApacheConfigurator._add_parser_mod`)
currently defined modules (`.ApacheConfigurator._add_parser_mod`)
This would only present a major problem in extremely atypical
configs that use ifmod for the missing deps.
@@ -56,7 +56,7 @@ class NginxConfigurator(common.Plugin):
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Nginx Web Server"
description = "Nginx Web Server - Alpha"
@classmethod
def add_parser_arguments(cls, add):
+2 -2
View File
@@ -92,13 +92,13 @@ def report_new_account(acc, config):
"contain certificates and private keys obtained by Let's Encrypt "
"so making regular backups of this folder is ideal.".format(
config.config_dir),
reporter.MEDIUM_PRIORITY, True)
reporter.MEDIUM_PRIORITY)
if acc.regr.body.emails:
recovery_msg = ("If you lose your account credentials, you can "
"recover through e-mails sent to {0}.".format(
", ".join(acc.regr.body.emails)))
reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY, True)
reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY)
class AccountMemoryStorage(interfaces.AccountStorage):
+1 -1
View File
@@ -531,7 +531,7 @@ def _report_failed_challs(failed_achalls):
reporter = zope.component.getUtility(interfaces.IReporter)
for achalls in problems.itervalues():
reporter.add_message(
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY, True)
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY)
def _generate_failed_chall_msg(failed_achalls):
+13 -2
View File
@@ -267,6 +267,14 @@ def _treat_as_renewal(config, domains):
return None
def _report_new_cert(cert_path):
"""Reports the creation of a new certificate to the user."""
reporter_util = zope.component.getUtility(interfaces.IReporter)
reporter_util.add_message("Congratulations! Your certificate has been "
"saved at {0}.".format(cert_path),
reporter_util.MEDIUM_PRIORITY)
def _auth_from_domains(le_client, config, domains, plugins):
"""Authenticate and enroll certificate."""
# Note: This can raise errors... caught above us though.
@@ -292,6 +300,8 @@ def _auth_from_domains(le_client, config, domains, plugins):
if not lineage:
raise errors.Error("Certificate could not be obtained")
_report_new_cert(lineage.cert)
return lineage
@@ -365,6 +375,7 @@ def auth(args, config, plugins):
file=args.csr[0], data=args.csr[1], form="der"))
le_client.save_certificate(
certr, chain, args.cert_path, args.chain_path)
_report_new_cert(args.cert_path)
else:
domains = _find_domains(args, installer)
_auth_from_domains(le_client, config, domains, plugins)
@@ -420,7 +431,7 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print
logger.debug("Expected interfaces: %s", args.ifaces)
ifaces = [] if args.ifaces is None else args.ifaces
filtered = plugins.ifaces(ifaces)
filtered = plugins.visible().ifaces(ifaces)
logger.debug("Filtered plugins: %r", filtered)
if not args.init and not args.prepare:
@@ -516,7 +527,7 @@ class HelpfulArgumentParser(object):
help2 = self.prescan_for_flag("--help", self.help_topics)
assert max(True, "a") == "a", "Gravity changed direction"
help_arg = max(help1, help2)
if help_arg == True:
if help_arg is True:
# just --help with no topic; avoid argparse altogether
print USAGE
sys.exit(0)
+1 -1
View File
@@ -286,7 +286,7 @@ class Client(object):
"configured in the directories under {0}.").format(
cert.cli_config.renewal_configs_dir)
reporter = zope.component.getUtility(interfaces.IReporter)
reporter.add_message(msg, reporter.LOW_PRIORITY, True)
reporter.add_message(msg, reporter.LOW_PRIORITY)
def save_certificate(self, certr, chain_cert, cert_path, chain_path):
# pylint: disable=no-self-use
+1 -1
View File
@@ -65,7 +65,7 @@ def pick_plugin(config, default, plugins, question, ifaces):
# throw more UX-friendly error if default not in plugins
filtered = plugins.filter(lambda p_ep: p_ep.name == default)
else:
filtered = plugins.ifaces(ifaces)
filtered = plugins.visible().ifaces(ifaces)
filtered.init(config)
verified = filtered.verify(ifaces)
+1 -1
View File
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
# immediately.
_SIGNALS = ([signal.SIGTERM] if os.name == "nt" else
[signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT,
signal.SIGXCPU, signal.SIGXFSZ, signal.SIGPWR])
signal.SIGXCPU, signal.SIGXFSZ])
class ErrorHandler(object):
+1 -1
View File
@@ -478,7 +478,7 @@ class IReporter(zope.interface.Interface):
LOW_PRIORITY = zope.interface.Attribute(
"Used to denote low priority messages")
def add_message(self, msg, priority, on_crash=False):
def add_message(self, msg, priority, on_crash=True):
"""Adds msg to the list of messages to be printed.
:param str msg: Message to be displayed to the user.
+9
View File
@@ -50,6 +50,11 @@ class PluginEntryPoint(object):
"""Description with name. Handy for UI."""
return "{0} ({1})".format(self.description, self.name)
@property
def hidden(self):
"""Should this plugin be hidden from UI?"""
return getattr(self.plugin_cls, "hidden", False)
def ifaces(self, *ifaces_groups):
"""Does plugin implements specified interface groups?"""
return not ifaces_groups or any(
@@ -183,6 +188,10 @@ class PluginsRegistry(collections.Mapping):
return type(self)(dict((name, plugin_ep) for name, plugin_ep
in self._plugins.iteritems() if pred(plugin_ep)))
def visible(self):
"""Filter plugins based on visibility."""
return self.filter(lambda plugin_ep: not plugin_ep.hidden)
def ifaces(self, *ifaces_groups):
"""Filter plugins based on interfaces."""
# pylint: disable=star-args
+2
View File
@@ -182,6 +182,8 @@ binary for temporary key/certificate generation.""".replace("\n", "")
achall.account_key.public_key(), self.config.simple_http_port):
return response
else:
logger.error(
"Self-verify of challenge failed, authorization abandoned.")
if self.conf("test-mode") and self._httpd.poll() is not None:
# simply verify cause command failure...
return False
+1
View File
@@ -17,6 +17,7 @@ class Installer(common.Plugin):
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Null Installer"
hidden = True
# pylint: disable=missing-docstring,no-self-use
+1 -1
View File
@@ -36,7 +36,7 @@ class Reporter(object):
def __init__(self):
self.messages = Queue.PriorityQueue()
def add_message(self, msg, priority, on_crash=False):
def add_message(self, msg, priority, on_crash=True):
"""Adds msg to the list of messages to be printed.
:param str msg: Message to be displayed to the user.
+110 -33
View File
@@ -16,6 +16,9 @@ from letsencrypt.tests import renewer_test
from letsencrypt.tests import test_util
CSR = test_util.vector_path('csr.der')
class CLITest(unittest.TestCase):
"""Tests for different commands."""
@@ -67,40 +70,114 @@ class CLITest(unittest.TestCase):
for r in xrange(len(flags)))):
self._call(['plugins'] + list(args))
@mock.patch("letsencrypt.cli.sys")
def test_auth_bad_args(self):
ret, _, _, _ = self._call(['-d', 'foo.bar', 'auth', '--csr', CSR])
self.assertEqual(ret, '--domains and --csr are mutually exclusive')
ret, _, _, _ = self._call(['-a', 'bad_auth', 'auth'])
self.assertEqual(ret, 'Authenticator could not be determined')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
def test_auth_new_request_success(self, mock_get_utility):
cert_path = '/etc/letsencrypt/live/foo.bar'
mock_lineage = mock.MagicMock(cert=cert_path)
mock_client = mock.MagicMock()
mock_client.obtain_and_enroll_certificate.return_value = mock_lineage
self._auth_new_request_common(mock_client)
self.assertEqual(
mock_client.obtain_and_enroll_certificate.call_count, 1)
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
def test_auth_new_request_failure(self):
mock_client = mock.MagicMock()
mock_client.obtain_and_enroll_certificate.return_value = False
self.assertRaises(errors.Error,
self._auth_new_request_common, mock_client)
def _auth_new_request_common(self, mock_client):
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
mock_renewal.return_value = None
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
mock_init.return_value = mock_client
self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth'])
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')
@mock.patch('letsencrypt.cli._init_le_client')
def test_auth_renewal(self, mock_init, mock_renewal, mock_get_utility):
cert_path = '/etc/letsencrypt/live/foo.bar'
mock_lineage = mock.MagicMock(cert=cert_path)
mock_cert = mock.MagicMock(body='body')
mock_key = mock.MagicMock(pem='pem_key')
mock_renewal.return_value = mock_lineage
mock_client = mock.MagicMock()
mock_client.obtain_certificate.return_value = (mock_cert, 'chain',
mock_key, 'csr')
mock_init.return_value = mock_client
with mock.patch('letsencrypt.cli.OpenSSL'):
with mock.patch('letsencrypt.cli.crypto_util'):
self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth'])
mock_client.obtain_certificate.assert_called_once_with(['foo.bar'])
self.assertEqual(mock_lineage.save_successor.call_count, 1)
mock_lineage.update_all_links_to.assert_called_once_with(
mock_lineage.latest_common_version())
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.display_ops.pick_installer')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._init_le_client')
def test_auth_csr(self, mock_init, mock_get_utility, mock_pick_installer):
cert_path = '/etc/letsencrypt/live/foo.bar'
mock_client = mock.MagicMock()
mock_client.obtain_certificate_from_csr.return_value = ('certr',
'chain')
mock_init.return_value = mock_client
installer = 'installer'
self._call(
['-a', 'standalone', '-i', installer, 'auth', '--csr', CSR,
'--cert-path', cert_path, '--chain-path', '/'])
self.assertEqual(mock_pick_installer.call_args[0][1], installer)
mock_client.save_certificate.assert_called_once_with(
'certr', 'chain', cert_path, '/')
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.sys')
def test_handle_exception(self, mock_sys):
# pylint: disable=protected-access
from letsencrypt import cli
mock_open = mock.mock_open()
with mock.patch("letsencrypt.cli.open", mock_open, create=True):
exception = Exception("detail")
with mock.patch('letsencrypt.cli.open', mock_open, create=True):
exception = Exception('detail')
cli._handle_exception(
Exception, exc_value=exception, trace=None, args=None)
mock_open().write.assert_called_once_with("".join(
mock_open().write.assert_called_once_with(''.join(
traceback.format_exception_only(Exception, exception)))
error_msg = mock_sys.exit.call_args_list[0][0][0]
self.assertTrue("unexpected error" in error_msg)
self.assertTrue('unexpected error' in error_msg)
with mock.patch("letsencrypt.cli.open", mock_open, create=True):
with mock.patch('letsencrypt.cli.open', mock_open, create=True):
mock_open.side_effect = [KeyboardInterrupt]
error = errors.Error("detail")
error = errors.Error('detail')
cli._handle_exception(
errors.Error, exc_value=error, trace=None, args=None)
# assert_any_call used because sys.exit doesn't exit in cli.py
mock_sys.exit.assert_any_call("".join(
mock_sys.exit.assert_any_call(''.join(
traceback.format_exception_only(errors.Error, error)))
args = mock.MagicMock(debug=False)
cli._handle_exception(
Exception, exc_value=Exception("detail"), trace=None, args=args)
Exception, exc_value=Exception('detail'), trace=None, args=args)
error_msg = mock_sys.exit.call_args_list[-1][0][0]
self.assertTrue("unexpected error" in error_msg)
self.assertTrue('unexpected error' in error_msg)
interrupt = KeyboardInterrupt("detail")
interrupt = KeyboardInterrupt('detail')
cli._handle_exception(
KeyboardInterrupt, exc_value=interrupt, trace=None, args=None)
mock_sys.exit.assert_called_with("".join(
mock_sys.exit.assert_called_with(''.join(
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
@@ -110,13 +187,13 @@ class DetermineAccountTest(unittest.TestCase):
def setUp(self):
self.args = mock.MagicMock(account=None, email=None)
self.config = configuration.NamespaceConfig(self.args)
self.accs = [mock.MagicMock(id="x"), mock.MagicMock(id="y")]
self.accs = [mock.MagicMock(id='x'), mock.MagicMock(id='y')]
self.account_storage = account.AccountMemoryStorage()
def _call(self):
# pylint: disable=protected-access
from letsencrypt.cli import _determine_account
with mock.patch("letsencrypt.cli.account.AccountFileStorage") as mock_storage:
with mock.patch('letsencrypt.cli.account.AccountFileStorage') as mock_storage:
mock_storage.return_value = self.account_storage
return _determine_account(self.args, self.config)
@@ -133,7 +210,7 @@ class DetermineAccountTest(unittest.TestCase):
self.assertEqual(self.accs[0].id, self.args.account)
self.assertTrue(self.args.email is None)
@mock.patch("letsencrypt.client.display_ops.choose_account")
@mock.patch('letsencrypt.client.display_ops.choose_account')
def test_multiple_accounts(self, mock_choose_accounts):
for acc in self.accs:
self.account_storage.save(acc)
@@ -144,11 +221,11 @@ class DetermineAccountTest(unittest.TestCase):
self.assertEqual(self.accs[1].id, self.args.account)
self.assertTrue(self.args.email is None)
@mock.patch("letsencrypt.client.display_ops.get_email")
@mock.patch('letsencrypt.client.display_ops.get_email')
def test_no_accounts_no_email(self, mock_get_email):
mock_get_email.return_value = "foo@bar.baz"
mock_get_email.return_value = 'foo@bar.baz'
with mock.patch("letsencrypt.cli.client") as client:
with mock.patch('letsencrypt.cli.client') as client:
client.register.return_value = (
self.accs[0], mock.sentinel.acme)
self.assertEqual((self.accs[0], mock.sentinel.acme), self._call())
@@ -156,15 +233,15 @@ class DetermineAccountTest(unittest.TestCase):
self.config, self.account_storage, tos_cb=mock.ANY)
self.assertEqual(self.accs[0].id, self.args.account)
self.assertEqual("foo@bar.baz", self.args.email)
self.assertEqual('foo@bar.baz', self.args.email)
def test_no_accounts_email(self):
self.args.email = "other email"
with mock.patch("letsencrypt.cli.client") as client:
self.args.email = 'other email'
with mock.patch('letsencrypt.cli.client') as client:
client.register.return_value = (self.accs[1], mock.sentinel.acme)
self._call()
self.assertEqual(self.accs[1].id, self.args.account)
self.assertEqual("other email", self.args.email)
self.assertEqual('other email', self.args.email)
class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest):
@@ -178,36 +255,36 @@ class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest):
def tearDown(self):
shutil.rmtree(self.tempdir)
@mock.patch("letsencrypt.le_util.make_or_verify_dir")
@mock.patch('letsencrypt.le_util.make_or_verify_dir')
def test_find_duplicative_names(self, unused_makedir):
from letsencrypt.cli import _find_duplicative_certs
test_cert = test_util.load_vector("cert-san.pem")
with open(self.test_rc.cert, "w") as f:
test_cert = test_util.load_vector('cert-san.pem')
with open(self.test_rc.cert, 'w') as f:
f.write(test_cert)
# No overlap at all
result = _find_duplicative_certs(["wow.net", "hooray.org"],
result = _find_duplicative_certs(['wow.net', 'hooray.org'],
self.config, self.cli_config)
self.assertEqual(result, (None, None))
# Totally identical
result = _find_duplicative_certs(["example.com", "www.example.com"],
result = _find_duplicative_certs(['example.com', 'www.example.com'],
self.config, self.cli_config)
self.assertTrue(result[0].configfile.filename.endswith("example.org.conf"))
self.assertTrue(result[0].configfile.filename.endswith('example.org.conf'))
self.assertEqual(result[1], None)
# Superset
result = _find_duplicative_certs(["example.com", "www.example.com",
"something.new"], self.config,
result = _find_duplicative_certs(['example.com', 'www.example.com',
'something.new'], self.config,
self.cli_config)
self.assertEqual(result[0], None)
self.assertTrue(result[1].configfile.filename.endswith("example.org.conf"))
self.assertTrue(result[1].configfile.filename.endswith('example.org.conf'))
# Partial overlap doesn't count
result = _find_duplicative_certs(["example.com", "something.new"],
result = _find_duplicative_certs(['example.com', 'something.new'],
self.config, self.cli_config)
self.assertEqual(result, (None, None))
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main() # pragma: no cover
+7 -5
View File
@@ -84,7 +84,7 @@ class PickPluginTest(unittest.TestCase):
def test_no_default(self):
self._call()
self.assertEqual(1, self.reg.ifaces.call_count)
self.assertEqual(1, self.reg.visible().ifaces.call_count)
def test_no_candidate(self):
self.assertTrue(self._call() is None)
@@ -94,7 +94,8 @@ class PickPluginTest(unittest.TestCase):
plugin_ep.init.return_value = "foo"
plugin_ep.misconfigured = False
self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep}
self.reg.visible().ifaces().verify().available.return_value = {
"bar": plugin_ep}
self.assertEqual("foo", self._call())
def test_single_misconfigured(self):
@@ -102,13 +103,14 @@ class PickPluginTest(unittest.TestCase):
plugin_ep.init.return_value = "foo"
plugin_ep.misconfigured = True
self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep}
self.reg.visible().ifaces().verify().available.return_value = {
"bar": plugin_ep}
self.assertTrue(self._call() is None)
def test_multiple(self):
plugin_ep = mock.MagicMock()
plugin_ep.init.return_value = "foo"
self.reg.ifaces().verify().available.return_value = {
self.reg.visible().ifaces().verify().available.return_value = {
"bar": plugin_ep,
"baz": plugin_ep,
}
@@ -119,7 +121,7 @@ class PickPluginTest(unittest.TestCase):
[plugin_ep, plugin_ep], self.question)
def test_choose_plugin_none(self):
self.reg.ifaces().verify().available.return_value = {
self.reg.visible().ifaces().verify().available.return_value = {
"bar": None,
"baz": None,
}
+5 -3
View File
@@ -82,9 +82,11 @@ class ReporterTest(unittest.TestCase):
self.assertTrue("Low" not in output)
def _add_messages(self):
self.reporter.add_message("High", self.reporter.HIGH_PRIORITY, True)
self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY)
self.reporter.add_message("Low", self.reporter.LOW_PRIORITY)
self.reporter.add_message("High", self.reporter.HIGH_PRIORITY)
self.reporter.add_message(
"Med", self.reporter.MEDIUM_PRIORITY, on_crash=False)
self.reporter.add_message(
"Low", self.reporter.LOW_PRIORITY, on_crash=False)
if __name__ == "__main__":
-1
View File
@@ -118,7 +118,6 @@ setup(
],
'letsencrypt.plugins': [
'manual = letsencrypt.plugins.manual:Authenticator',
# TODO: null should probably not be presented to the user
'null = letsencrypt.plugins.null:Installer',
'standalone = letsencrypt.plugins.standalone.authenticator'
':StandaloneAuthenticator',
+7 -1
View File
@@ -4,11 +4,17 @@
# ugh, go version output is like:
# go version go1.4.2 linux/amd64
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
# version comparison
function verlte {
#OS X doesn't support version sorting; emulate with sed
if [ `uname` == 'Darwin' ]; then
[ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \
| sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ]
else
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
fi
}
if ! verlte 1.5 "$GOVER" ; then
+1 -1
View File
@@ -16,7 +16,7 @@ fi
cover () {
if [ "$1" = "letsencrypt" ]; then
min=96
min=97
elif [ "$1" = "acme" ]; then
min=100
elif [ "$1" = "letsencrypt_apache" ]; then