Merge remote-tracking branch 'origin/denest' into experiment

This commit is contained in:
Peter Eckersley
2016-02-04 18:50:06 -08:00
7 changed files with 220 additions and 121 deletions
+59 -29
View File
@@ -175,7 +175,7 @@ def _determine_account(config):
acc = accounts[0]
else: # no account registered yet
if config.email is None and not config.register_unsafely_without_email:
config.email = display_ops.get_email()
config.namespace.email = display_ops.get_email()
def _tos_cb(regr):
if config.tos:
@@ -197,7 +197,7 @@ def _determine_account(config):
raise errors.Error(
"Unable to register an account with ACME server")
config.account = acc.id
config.namespace.account = acc.id
return acc, acme
@@ -229,7 +229,7 @@ def _find_duplicative_certs(config, domains):
candidate_lineage = storage.RenewableCert(renewal_file, cli_config)
except (errors.CertStorageError, IOError):
logger.warning("Renewal conf file %s is broken. Skipping.", renewal_file)
logger.info("Traceback was:\n%s", traceback.format_exc())
logger.debug("Traceback was:\n%s", traceback.format_exc())
continue
# TODO: Handle these differently depending on whether they are
# expired or still valid?
@@ -248,8 +248,10 @@ def _find_duplicative_certs(config, domains):
def _treat_as_renewal(config, domains):
"""Determine whether there are duplicated names and how to handle them
(renew, reinstall, newcert, or no action).
"""Determine whether there are duplicated names and how to handle
them (renew, reinstall, newcert, or raising an error to stop
the client run if the user chooses to cancel the operation when
prompted).
:returns: Two-element tuple containing desired new-certificate behavior as
a string token ("reinstall", "renew", or "newcert"), plus either
@@ -457,8 +459,8 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
lineage.save_successor(
lineage.latest_common_version(), OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain),
configuration.RenewerConfiguration(config.namespace))
lineage.update_all_links_to(lineage.latest_common_version())
# TODO: Check return value of save_successor
# TODO: Also update lineage renewal config with any relevant
@@ -725,7 +727,15 @@ def install(config, plugins):
le_client.enhance_config(domains, config)
def _restore_required_config_elements(full_path, config, renewalparams):
def _restore_required_config_elements(config, renewalparams):
"""Sets non-plugin specific values in config from renewalparams
:param configuration.NamespaceConfig config: configuration for the
current lineage
:param configobj.Section renewalparams: Parameters from the renewal
configuration file that defines this lineage
"""
# string-valued items to add if they're present
for config_item in STR_CONFIG_ITEMS:
if config_item in renewalparams:
@@ -742,12 +752,18 @@ def _restore_required_config_elements(full_path, config, renewalparams):
value = int(renewalparams[config_item])
setattr(config.namespace, config_item, value)
except ValueError:
logger.warning("Renewal configuration file %s specifies "
"a non-numeric value for %s. Skipping.",
full_path, config_item)
raise
raise errors.Error(
"Expected a numeric value for {0}".format(config_item))
def _restore_plugin_configs(config, renewalparams):
"""Sets plugin specific values in config from renewalparams
:param configuration.NamespaceConfig config: configuration for the
current lineage
:param configobj.Section renewalparams: Parameters from the renewal
configuration file that defines this lineage
"""
# Now use parser to get plugin-prefixed items with correct types
# XXX: the current approach of extracting only prefixed items
# related to the actually-used installer and authenticator
@@ -780,7 +796,7 @@ def _restore_plugin_configs(config, renewalparams):
return True
def _reconstitute(full_path, config):
def _reconstitute(config, full_path):
"""Try to instantiate a RenewableCert, updating config with relevant items.
This is specifically for use in renewal and enforces several checks
@@ -788,12 +804,18 @@ def _reconstitute(full_path, config):
request. The config argument is modified by including relevant options
read from the renewal configuration file.
:param configuration.NamespaceConfig config: configuration for the
current lineage
:param str full_path: Absolute path to the configuration file that
defines this lineage
:returns: the RenewableCert object or None if a fatal error occurred
:rtype: `storage.RenewableCert` or NoneType
"""
"""
try:
renewal_candidate = storage.RenewableCert(full_path, config)
renewal_candidate = storage.RenewableCert(
full_path, configuration.RenewerConfiguration(config))
except (errors.CertStorageError, IOError):
logger.warning("Renewal configuration file %s is broken. Skipping.", full_path)
logger.info("Traceback was:\n%s", traceback.format_exc())
@@ -810,11 +832,13 @@ def _reconstitute(full_path, config):
# Now restore specific values along with their data types, if
# those elements are present.
try:
_restore_required_config_elements(full_path, config, renewalparams)
_restore_required_config_elements(config, renewalparams)
_restore_plugin_configs(config, renewalparams)
except ValueError:
# There was a data type error which has already been
# logged.
except (ValueError, errors.Error) as error:
logger.warning(
"An error occured while parsing %s. The error was %s. "
"Skipping the file.", full_path, error.message)
logger.debug("Traceback was:\n%s", traceback.format_exc())
return None
# webroot_map is, uniquely, a dict, and the general-purpose
@@ -840,14 +864,13 @@ def _renewal_conf_files(config):
"""Return /path/to/*.conf in the renewal conf directory"""
return glob.glob(os.path.join(config.renewal_configs_dir, "*.conf"))
def _rc_from_config(config):
def _copy_nsconfig(config):
ns = copy.deepcopy(config.namespace)
new_config = configuration.NamespaceConfig(ns)
return configuration.RenewerConfiguration(new_config)
return new_config
def renew(cli_config, unused_plugins):
def renew(config, unused_plugins):
"""Renew previously-obtained certificates."""
config = _rc_from_config(cli_config)
if config.domains != []:
raise errors.Error("Currently, the renew verb is only capable of "
"renewing all installed certificates that are due "
@@ -856,19 +879,24 @@ def renew(cli_config, unused_plugins):
"renew specific certificates, use the certonly "
"command. The renew verb may provide other options "
"for selecting certificates to renew in the future.")
for renewal_file in _renewal_conf_files(config):
if config.csr is not None:
raise errors.Error("Currently, the renew verb cannot be used when "
"specifying a CSR file. Please try the certonly "
"command instead.")
config.noninteractive_mode = True
renewer_config = configuration.RenewerConfiguration(config)
for renewal_file in _renewal_conf_files(renewer_config):
if not renewal_file.endswith(".conf"):
continue
print("Processing " + renewal_file)
# XXX: does this succeed in making a fully independent config object
# each time?
config = _rc_from_config(cli_config)
config.noninteractive_mode = True
lineage_config = _copy_nsconfig(config)
# Note that this modifies config (to add back the configuration
# elements from within the renewal configuration file).
try:
renewal_candidate = _reconstitute(renewal_file, config)
renewal_candidate = _reconstitute(lineage_config, renewal_file)
except Exception as e: # pylint: disable=broad-except
# reconstitute encountered an unanticipated problem.
logger.warning("Renewal configuration file %s produced an "
@@ -881,14 +909,14 @@ def renew(cli_config, unused_plugins):
# already been logged. Go on to the next config.
continue
# XXX: ensure that each call here replaces the previous one
zope.component.provideUtility(config)
zope.component.provideUtility(lineage_config)
print("Trying...")
# Because obtain_cert itself indirectly decides whether to renew
# or not, we couldn't currently make a UI/logging distinction at
# this stage to indicate whether renewal was actually attempted
# (or successful).
obtain_cert(config, plugins_disco.PluginsRegistry.find_all(),
obtain_cert(lineage_config, plugins_disco.PluginsRegistry.find_all(),
renewal_candidate)
def revoke(config, unused_plugins): # TODO: coop with renewal config
@@ -1704,6 +1732,8 @@ def main(cli_args=sys.argv[1:]):
displayer = display_util.NoninteractiveDisplay(sys.stdout)
elif config.text_mode:
displayer = display_util.FileDisplay(sys.stdout)
elif config.verb == "renew":
displayer = display_util.NoninteractiveDisplay(sys.stdout)
else:
displayer = display_util.NcursesDisplay()
zope.component.provideUtility(displayer)
+5 -15
View File
@@ -249,7 +249,7 @@ class Client(object):
`.register` must be called before `.obtain_certificate`
:param set domains: domains to get a certificate
:param list domains: domains to get a certificate
:returns: `.CertificateResource`, certificate chain (as
returned by `.fetch_chain`), and newly generated private key
@@ -282,23 +282,13 @@ class Client(object):
"""
certr, chain, key, _ = self.obtain_certificate(domains)
# XXX: We clearly need a more general and correct way of getting
# options into the configobj for the RenewableCert instance.
# This is a quick-and-dirty way to do it to allow integration
# testing to start. (Note that the config parameter to new_lineage
# ideally should be a ConfigObj, but in this case a dict will be
# accepted in practice.)
params = vars(self.config.namespace)
config = {}
cli_config = configuration.RenewerConfiguration(self.config.namespace)
if (cli_config.config_dir != constants.CLI_DEFAULTS["config_dir"] or
cli_config.work_dir != constants.CLI_DEFAULTS["work_dir"]):
if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or
self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]):
logger.warning(
"Non-standard path(s), might not work with crontab installed "
"by your operating system package manager")
if cli_config.dry_run:
if self.config.dry_run:
logger.info("Dry run: Skipping creating new lineage for %s",
domains[0])
return None
@@ -307,7 +297,7 @@ class Client(object):
domains[0], OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped),
key.pem, crypto_util.dump_pyopenssl_chain(chain),
params, config, cli_config)
configuration.RenewerConfiguration(self.config.namespace))
def save_certificate(self, certr, chain_cert,
cert_path, chain_path, fullchain_path):
-5
View File
@@ -84,15 +84,10 @@ class RenewerConfiguration(object):
def __init__(self, namespace):
self.namespace = namespace
# We're done setting up the attic. Now pull up the ladder after ourselves...
self.__setattr__ = self.__setattr_implementation__
def __getattr__(self, name):
return getattr(self.namespace, name)
def __setattr_implementation__(self, var, value):
return self.namespace.__setattr__(var, value)
@property
def archive_dir(self): # pylint: disable=missing-docstring
return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR)
+79 -31
View File
@@ -50,6 +50,68 @@ def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()):
return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0]
def write_renewal_config(filename, target, cli_config):
"""Writes a renewal config file with the specified name and values.
:param str filename: Absolute path to the config file
:param dict target: Maps ALL_FOUR to their symlink paths
:param .RenewerConfiguration cli_config: parsed command line
arguments
:returns: Configuration object for the new config file
:rtype: configobj.ConfigObj
"""
# create_empty creates a new config file if filename does not exist
config = configobj.ConfigObj(filename, create_empty=True)
for kind in ALL_FOUR:
config[kind] = target[kind]
# XXX: We clearly need a more general and correct way of getting
# options into the configobj for the RenewableCert instance.
# This is a quick-and-dirty way to do it to allow integration
# testing to start. (Note that the config parameter to new_lineage
# ideally should be a ConfigObj, but in this case a dict will be
# accepted in practice.)
renewalparams = vars(cli_config.namespace)
if renewalparams:
config["renewalparams"] = renewalparams
config.comments["renewalparams"] = ["",
"Options and defaults used"
" in the renewal process"]
# TODO: add human-readable comments explaining other available
# parameters
logger.debug("Writing new config %s.", filename)
config.write()
return config
def update_configuration(lineagename, target, cli_config):
"""Modifies lineagename's config to contain the specified values.
:param str lineagename: Name of the lineage being modified
:param dict target: Maps ALL_FOUR to their symlink paths
:param .RenewerConfiguration cli_config: parsed command line
arguments
:returns: Configuration object for the updated config file
:rtype: configobj.ConfigObj
"""
config_filename = os.path.join(
cli_config.renewal_configs_dir, lineagename) + ".conf"
temp_filename = config_filename + ".new"
# If an existing tempfile exists, delete it
if os.path.exists(temp_filename):
os.unlink(temp_filename)
write_renewal_config(temp_filename, target, cli_config)
os.rename(temp_filename, config_filename)
return configobj.ConfigObj(config_filename)
class RenewableCert(object): # pylint: disable=too-many-instance-attributes
"""Renewable certificate.
@@ -589,9 +651,8 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
return False
@classmethod
def new_lineage(cls, lineagename, cert, privkey, chain,
renewalparams=None, config=None, cli_config=None):
# pylint: disable=too-many-locals,too-many-arguments
def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):
# pylint: disable=too-many-locals
"""Create a new certificate lineage.
Attempts to create a certificate lineage -- enrolled for
@@ -611,26 +672,13 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
:param str cert: the initial certificate version in PEM format
:param str privkey: the private key in PEM format
:param str chain: the certificate chain in PEM format
:param configobj.ConfigObj renewalparams: parameters that
should be used when instantiating authenticator and installer
objects in the future to attempt to renew this cert or deploy
new versions of it
:param configobj.ConfigObj config: renewal configuration
defaults, affecting, for example, the locations of the
directories where the associated files will be saved
:param .RenewerConfiguration cli_config: parsed command line
arguments
:returns: the newly-created RenewalCert object
:rtype: :class:`storage.renewableCert`"""
config = config_with_defaults(config)
# This attempts to read the renewer config file and augment or replace
# the renewer defaults with any options contained in that file. If
# renewer_config_file is undefined or if the file is nonexistent or
# empty, this .merge() will have no effect.
config.merge(configobj.ConfigObj(cli_config.renewer_config_file))
:rtype: :class:`storage.renewableCert`
"""
# Examine the configuration and find the new lineage's name
for i in (cli_config.renewal_configs_dir, cli_config.archive_dir,
cli_config.live_dir):
@@ -685,21 +733,11 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
# Document what we've done in a new renewal config file
config_file.close()
new_config = configobj.ConfigObj(config_filename, create_empty=True)
for kind in ALL_FOUR:
new_config[kind] = target[kind]
if renewalparams:
new_config["renewalparams"] = renewalparams
new_config.comments["renewalparams"] = ["",
"Options and defaults used"
" in the renewal process"]
# TODO: add human-readable comments explaining other available
# parameters
logger.debug("Writing new config %s.", config_filename)
new_config.write()
new_config = write_renewal_config(config_filename, target, cli_config)
return cls(new_config.filename, cli_config)
def save_successor(self, prior_version, new_cert, new_privkey, new_chain):
def save_successor(self, prior_version, new_cert,
new_privkey, new_chain, cli_config):
"""Save new cert and chain as a successor of a prior version.
Returns the new version number that was created.
@@ -715,6 +753,8 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
:param str new_privkey: the new private key, in PEM format,
or ``None``, if the private key has not changed
:param str new_chain: the new chain, in PEM format
:param .RenewerConfiguration cli_config: parsed command line
arguments
:returns: the new version number that was created
:rtype: int
@@ -726,6 +766,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
# if needed (ensuring their permissions are correct)
# Figure out what the new version is and hence where to save things
self.cli_config = cli_config
target_version = self.next_free_version()
archive = self.cli_config.archive_dir
# XXX if anyone ever moves a renewal configuration file, this will
@@ -766,4 +807,11 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
with open(target["fullchain"], "w") as f:
logger.debug("Writing full chain to %s.", target["fullchain"])
f.write(new_cert + new_chain)
symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR)
# Update renewal config file
self.configfile = update_configuration(
self.lineagename, symlinks, cli_config)
self.configuration = config_with_defaults(self.configfile)
return target_version
+30 -27
View File
@@ -504,8 +504,9 @@ class RenewableCertTests(BaseRenewableCertTest):
with open(where, "w") as f:
f.write(kind)
self.test_rc.update_all_links_to(3)
self.assertEqual(6, self.test_rc.save_successor(3, "new cert", None,
"new chain"))
self.assertEqual(
6, self.test_rc.save_successor(3, "new cert", None,
"new chain", self.cli_config))
with open(self.test_rc.version("cert", 6)) as f:
self.assertEqual(f.read(), "new cert")
with open(self.test_rc.version("chain", 6)) as f:
@@ -516,10 +517,12 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertFalse(os.path.islink(self.test_rc.version("privkey", 3)))
self.assertTrue(os.path.islink(self.test_rc.version("privkey", 6)))
# Let's try two more updates
self.assertEqual(7, self.test_rc.save_successor(6, "again", None,
"newer chain"))
self.assertEqual(8, self.test_rc.save_successor(7, "hello", None,
"other chain"))
self.assertEqual(
7, self.test_rc.save_successor(6, "again", None,
"newer chain", self.cli_config))
self.assertEqual(
8, self.test_rc.save_successor(7, "hello", None,
"other chain", self.cli_config))
# All of the subsequent versions should link directly to the original
# privkey.
for i in (6, 7, 8):
@@ -532,27 +535,33 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertEqual(self.test_rc.current_version(kind), 3)
# Test updating from latest version rather than old version
self.test_rc.update_all_links_to(8)
self.assertEqual(9, self.test_rc.save_successor(8, "last", None,
"attempt"))
self.assertEqual(
9, self.test_rc.save_successor(8, "last", None,
"attempt", self.cli_config))
for kind in ALL_FOUR:
self.assertEqual(self.test_rc.available_versions(kind),
range(1, 10))
self.assertEqual(self.test_rc.current_version(kind), 8)
with open(self.test_rc.version("fullchain", 9)) as f:
self.assertEqual(f.read(), "last" + "attempt")
temp_config_file = os.path.join(self.cli_config.renewal_configs_dir,
self.test_rc.lineagename) + ".conf.new"
with open(temp_config_file, "w") as f:
f.write("We previously crashed while writing me :(")
# Test updating when providing a new privkey. The key should
# be saved in a new file rather than creating a new symlink.
self.assertEqual(10, self.test_rc.save_successor(9, "with", "a",
"key"))
self.assertEqual(
10, self.test_rc.save_successor(9, "with", "a",
"key", self.cli_config))
self.assertTrue(os.path.exists(self.test_rc.version("privkey", 10)))
self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10)))
self.assertFalse(os.path.exists(temp_config_file))
def test_new_lineage(self):
"""Test for new_lineage() class method."""
from letsencrypt import storage
result = storage.RenewableCert.new_lineage(
"the-lineage.com", "cert", "privkey", "chain", None,
self.defaults, self.cli_config)
"the-lineage.com", "cert", "privkey", "chain", self.cli_config)
# This consistency check tests most relevant properties about the
# newly created cert lineage.
# pylint: disable=protected-access
@@ -563,27 +572,23 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertEqual(f.read(), "cert" + "chain")
# Let's do it again and make sure it makes a different lineage
result = storage.RenewableCert.new_lineage(
"the-lineage.com", "cert2", "privkey2", "chain2", None,
self.defaults, self.cli_config)
"the-lineage.com", "cert2", "privkey2", "chain2", self.cli_config)
self.assertTrue(os.path.exists(os.path.join(
self.cli_config.renewal_configs_dir, "the-lineage.com-0001.conf")))
# Now trigger the detection of already existing files
os.mkdir(os.path.join(
self.cli_config.live_dir, "the-lineage.com-0002"))
self.assertRaises(errors.CertStorageError,
storage.RenewableCert.new_lineage,
"the-lineage.com", "cert3", "privkey3", "chain3",
None, self.defaults, self.cli_config)
storage.RenewableCert.new_lineage, "the-lineage.com",
"cert3", "privkey3", "chain3", self.cli_config)
os.mkdir(os.path.join(self.cli_config.archive_dir, "other-example.com"))
self.assertRaises(errors.CertStorageError,
storage.RenewableCert.new_lineage,
"other-example.com", "cert4", "privkey4", "chain4",
None, self.defaults, self.cli_config)
"other-example.com", "cert4",
"privkey4", "chain4", self.cli_config)
# Make sure it can accept renewal parameters
params = {"stuff": "properties of stuff", "great": "awesome"}
result = storage.RenewableCert.new_lineage(
"the-lineage.com", "cert2", "privkey2", "chain2",
params, self.defaults, self.cli_config)
"the-lineage.com", "cert2", "privkey2", "chain2", self.cli_config)
# TODO: Conceivably we could test that the renewal parameters actually
# got saved
@@ -595,8 +600,7 @@ class RenewableCertTests(BaseRenewableCertTest):
shutil.rmtree(self.cli_config.live_dir)
storage.RenewableCert.new_lineage(
"the-lineage.com", "cert2", "privkey2", "chain2",
None, self.defaults, self.cli_config)
"the-lineage.com", "cert2", "privkey2", "chain2", self.cli_config)
self.assertTrue(os.path.exists(
os.path.join(
self.cli_config.renewal_configs_dir, "the-lineage.com.conf")))
@@ -610,9 +614,8 @@ class RenewableCertTests(BaseRenewableCertTest):
from letsencrypt import storage
mock_uln.return_value = "this_does_not_end_with_dot_conf", "yikes"
self.assertRaises(errors.CertStorageError,
storage.RenewableCert.new_lineage,
"example.com", "cert", "privkey", "chain",
None, self.defaults, self.cli_config)
storage.RenewableCert.new_lineage, "example.com",
"cert", "privkey", "chain", self.cli_config)
def test_bad_kind(self):
self.assertRaises(
+1 -1
View File
@@ -51,7 +51,7 @@ common renew
sed -i "4arenew_before_expiry = 10 years" "$root/conf/renewal/le1.wtf.conf"
common renew
# letsencrypt-renewer $store_flags
ls "$root/conf/archive/le1.wtf"
# dir="$root/conf/archive/le1.wtf"
# for x in cert chain fullchain privkey;
# do
+46 -13
View File
@@ -1,22 +1,55 @@
#!/bin/bash -x
# $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL are dynamically set at execution
# with curl, instance metadata available from EC2 metadata service:
#public_host=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-hostname)
#public_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-ipv4)
#private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4)
# $OS_TYPE $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL
# are dynamically set at execution
# run letsencrypt-apache2 via letsencrypt-auto
cd letsencrypt
./letsencrypt-auto certonly -v --standalone --debug \
--text --agree-dev-preview --agree-tos \
--renew-by-default --redirect \
--register-unsafely-without-email \
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL
./letsencrypt-auto renew --renew-by-default
export SUDO=sudo
if [ -f /etc/debian_version ] ; then
echo "Bootstrapping dependencies for Debian-based OSes..."
$SUDO bootstrap/_deb_common.sh
elif [ -f /etc/redhat-release ] ; then
echo "Bootstrapping dependencies for RedHat-based OSes..."
$SUDO bootstrap/_rpm_common.sh
else
echo "Dont have bootstrapping for this OS!"
exit 1
fi
ls /etc/letsencrypt/archive/$PUBLIC_HOSTNAME | grep -q 2.pem
bootstrap/dev/venv.sh
sudo venv/bin/letsencrypt certonly --debug --standalone -t --agree-dev-preview --agree-tos \
--renew-by-default --redirect --register-unsafely-without-email \
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL -v
if [ $? -ne 0 ] ; then
FAIL=1
fi
if [ "$OS_TYPE" = "ubuntu" ] ; then
venv/bin/tox -e apacheconftest
else
echo Not running hackish apache tests on $OS_TYPE
fi
if [ $? -ne 0 ] ; then
FAIL=1
fi
sudo venv/bin/letsencrypt renew --renew-by-default
if [ $? -ne 0 ] ; then
FAIL=1
fi
ls /etc/letsencrypt/archive/$PUBLIC_HOSTNAME | grep -q 2.pem
if [ $? -ne 0 ] ; then
FAIL=1
fi
# return error if any of the subtests failed
if [ "$FAIL" = 1 ] ; then
exit 1
fi