Refactor logging, "pythonic" DialogHandler

This commit is contained in:
Jakub Warmuz 2014-12-10 11:32:22 +01:00
parent cc85f22133
commit 7e0cee00b2
11 changed files with 237 additions and 333 deletions

View file

@ -1,5 +1,6 @@
"""Apache Configuration based off of Augeas Configurator."""
import hashlib
import logging
import os
import pkg_resources
import re
@ -15,7 +16,6 @@ from letsencrypt.client import CONFIG
from letsencrypt.client import crypto_util
from letsencrypt.client import errors
from letsencrypt.client import le_util
from letsencrypt.client import logger
# Configurator should be turned into a Singleton
@ -233,13 +233,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if len(path["cert_file"]) == 0 or len(path["cert_key"]) == 0:
# Throw some "can't find all of the directives error"
logger.warn(("Cannot find a cert or key directive in %s"
% vhost.path))
logger.warn("VirtualHost was not modified")
logging.warn(
"Cannot find a cert or key directive in %s", vhost.path)
logging.warn("VirtualHost was not modified")
# Presumably break here so that the virtualhost is not modified
return False
logger.info("Deploying Certificate to VirtualHost %s" % vhost.filep)
logging.info("Deploying Certificate to VirtualHost %s", vhost.filep)
self.aug.set(path["cert_file"][0], cert)
self.aug.set(path["cert_key"][0], key)
@ -530,15 +530,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
if not check_ssl_loaded():
logger.info("Loading mod_ssl into Apache Server")
logging.info("Loading mod_ssl into Apache Server")
enable_mod("ssl")
# Check for Listen 443
# Note: This could be made to also look for ip:443 combo
# TODO: Need to search only open directives and IfMod mod_ssl.c
if len(self.find_directive(case_i("Listen"), "443")) == 0:
logger.debug("No Listen 443 directive found")
logger.debug("Setting the Apache Server to Listen on port 443")
logging.debug("No Listen 443 directive found")
logging.debug("Setting the Apache Server to Listen on port 443")
path = self._add_dir_to_ifmodssl(
get_aug_path(self.location["listen"]), "Listen", "443")
self.save_notes += "Added Listen 443 directive to %s\n" % path
@ -560,15 +560,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
tup = addr.partition(":")
if tup[0] == "_default_":
if not self.is_name_vhost(default_addr):
logger.debug(("Setting all VirtualHosts on "
"%s to be name based vhosts" % default_addr))
logging.debug("Setting all VirtualHosts on %s to be "
"name based vhosts", default_addr)
self.add_name_vhost(default_addr)
# No default addresses... so set each one individually
for addr in vhost.addrs:
if not self.is_name_vhost(addr):
logger.debug(("Setting VirtualHost at %s "
"to be a name based virtual host" % addr))
logging.debug("Setting VirtualHost at %s to be a name "
"based virtual host", addr)
self.add_name_vhost(addr)
def _get_ifmod(self, aug_conf_path, mod):
@ -709,7 +709,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
# matchObj = validChars.match(arg)
# if matchObj.group() != arg:
# logger.error("Error: Invalid regexp characters in %s" % arg)
# logging.error("Error: Invalid regexp characters in %s", arg)
# return []
# Standardize the include argument based on server root
@ -775,7 +775,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
new_file.write(line)
new_file.write("</IfModule>\n")
except IOError:
logger.fatal("Error writing/reading to file in make_vhost_ssl")
logging.fatal("Error writing/reading to file in make_vhost_ssl")
sys.exit(49)
finally:
orig_file.close()
@ -809,7 +809,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
vh_p = self.aug.match(("/files%s//* [label()=~regexp('%s')]" %
(ssl_fp, case_i('VirtualHost'))))
if len(vh_p) != 1:
logger.error("Error: should only be one vhost in %s" % avail_fp)
logging.error("Error: should only be one vhost in %s", avail_fp)
sys.exit(1)
self.add_dir(vh_p[0], "SSLCertificateFile",
@ -819,7 +819,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.add_dir(vh_p[0], "Include", self.location["ssl_options"])
# Log actions and create save notes
logger.info("Created an SSL vhost at %s" % ssl_fp)
logging.info("Created an SSL vhost at %s", ssl_fp)
self.save_notes += 'Created ssl vhost at %s\n' % ssl_fp
self.save()
@ -838,7 +838,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if (self.is_name_vhost(nonssl_vhost.addrs[i]) and
not self.is_name_vhost(ssl_addrs[i])):
self.add_name_vhost(ssl_addrs[i])
logger.info("Enabling NameVirtualHosts on " + ssl_addrs[i])
logging.info("Enabling NameVirtualHosts on %s", ssl_addrs[i])
need_to_save = True
if need_to_save:
@ -868,7 +868,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
general_v = self._general_vhost(ssl_vhost)
if general_v is None:
# Add virtual_server with redirect
logger.debug(
logging.debug(
"Did not find http version of ssl virtual host... creating")
return self.create_redirect_vhost(ssl_vhost)
else:
@ -876,10 +876,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
exists, code = self.existing_redirect(general_v)
if exists:
if code == 0:
logger.debug("Redirect already added")
logging.debug("Redirect already added")
return True, general_v
else:
logger.debug("Unknown redirect exists for this vhost")
logging.debug("Unknown redirect exists for this vhost")
return False, general_v
# Add directives to server
self.add_dir(general_v.path, "RewriteEngine", "On")
@ -992,7 +992,7 @@ LogLevel warn \n\
# Write out file
with open(redirect_filepath, 'w') as redirect_fd:
redirect_fd.write(redirect_file)
logger.info("Created redirect file: " + redirect_filename)
logging.info("Created redirect file: %s", redirect_filename)
self.aug.load()
# Make a new vhost data structure and add it to the lists
@ -1121,8 +1121,8 @@ LogLevel warn \n\
# Can be removed once find directive can return ordered results
if len(cert_path) != 1 or len(key_path) != 1:
logger.error(("Too many cert or key directives in vhost "
"%s" % vhost.filep))
logging.error("Too many cert or key directives in vhost %s",
vhost.filep)
sys.exit(40)
cert = os.path.abspath(self.aug.get(cert_path[0]))
@ -1172,7 +1172,7 @@ LogLevel warn \n\
self.register_file_creation(False, enabled_path)
os.symlink(vhost.filep, enabled_path)
vhost.enabled = True
logger.info("Enabling available site: %s" % vhost.filep)
logging.info("Enabling available site: %s", vhost.filep)
self.save_notes += 'Enabled site %s\n' % vhost.filep
return True
return False
@ -1289,14 +1289,14 @@ LogLevel warn \n\
stderr=subprocess.PIPE)
text = proc.communicate()
except (OSError, ValueError):
logger.fatal("Unable to run /usr/sbin/apache2ctl configtest")
logging.fatal("Unable to run /usr/sbin/apache2ctl configtest")
sys.exit(1)
if proc.returncode != 0:
# Enter recovery routine...
logger.error("Configtest failed")
logger.error(text[0])
logger.error(text[1])
logging.error("Configtest failed")
logging.error(text[0])
logging.error(text[1])
return False
return True
@ -1384,8 +1384,8 @@ LogLevel warn \n\
# Do weak validation that challenge is of expected type
if not ("list_sni_tuple" in chall_dict and "dvsni_key" in chall_dict):
logger.fatal("Incorrect parameter given to Apache DVSNI challenge")
logger.fatal("Chall dict: " + str(chall_dict))
logging.fatal("Incorrect parameter given to Apache DVSNI challenge")
logging.fatal("Chall dict: %s", chall_dict)
sys.exit(1)
addresses = []
@ -1393,10 +1393,10 @@ LogLevel warn \n\
for tup in chall_dict["list_sni_tuple"]:
vhost = self.choose_virtual_host(tup[0])
if vhost is None:
logger.error(("No vhost exists with servername "
"or alias of: %s" % tup[0]))
logger.error("No _default_:443 vhost exists")
logger.error("Please specify servernames in the Apache config")
logging.error(
"No vhost exists with servername or alias of: %s", tup[0])
logging.error("No _default_:443 vhost exists")
logging.error("Please specify servernames in the Apache config")
return None
# TODO - @jdkasten review this code to make sure it makes sense
@ -1565,8 +1565,8 @@ def enable_mod(mod_name):
stdout=open("/dev/null", 'w'),
stderr=open("/dev/null", 'w'))
except (OSError, subprocess.CalledProcessError) as err:
logger.error("Error enabling mod_" + mod_name)
logger.error("Exception: %s" % str(err))
logging.error("Error enabling mod_%s", mod_name)
logging.error("Exception: %s", err)
sys.exit(1)
@ -1589,9 +1589,9 @@ def check_ssl_loaded():
stderr=open(
"/dev/null", 'w')).communicate()[0]
except (OSError, ValueError):
logger.error(
"Error accessing %s for loaded modules!" % CONFIG.APACHE_CTL)
logger.error("This may be caused by an Apache Configuration Error")
logging.error(
"Error accessing %s for loaded modules!", CONFIG.APACHE_CTL)
logging.error("This may be caused by an Apache Configuration Error")
return False
if "ssl_module" in proc:
@ -1614,14 +1614,14 @@ def apache_restart():
if proc.returncode != 0:
# Enter recovery routine...
logger.error("Configtest failed")
logger.error(text[0])
logger.error(text[1])
logging.error("Configtest failed")
logging.error(text[0])
logging.error(text[1])
return False
except (OSError, ValueError):
logger.fatal(("Apache Restart Failed - "
"Please Check the Configuration"))
logging.fatal(
"Apache Restart Failed - Please Check the Configuration")
sys.exit(1)
return True
@ -1720,8 +1720,6 @@ def main():
"""Main function used for quick testing purposes"""
config = ApacheConfigurator()
logger.setLogger(logger.FileLogger(sys.stdout))
logger.setLogLevel(logger.DEBUG)
# for v in config.vhosts:
# print v.filep

View file

@ -1,4 +1,5 @@
"""Class of Augeas Configurators."""
import logging
import os
import sys
import shutil
@ -9,7 +10,6 @@ import augeas
from letsencrypt.client import CONFIG
from letsencrypt.client import configurator
from letsencrypt.client import le_util
from letsencrypt.client import logger
class AugeasConfigurator(configurator.Configurator):
@ -61,9 +61,9 @@ class AugeasConfigurator(configurator.Configurator):
# As aug.get may return null
if lens_path and lens in lens_path:
# Strip off /augeas/files and /error
logger.error('There has been an error in parsing the file: '
'%s' % path[13:len(path) - 6])
logger.error(self.aug.get(path + '/message'))
logging.error('There has been an error in parsing the file: %s',
path[13:len(path) - 6])
logging.error(self.aug.get(path + '/message'))
def save(self, title=None, temporary=False):
"""Saves all changes to the configuration files.
@ -90,14 +90,14 @@ class AugeasConfigurator(configurator.Configurator):
except (RuntimeError, IOError):
# Check for the root of save problems
new_errs = self.aug.match("/augeas//error")
# logger.error("During Save - " + mod_conf)
# logging.error("During Save - %s", mod_conf)
# Only print new errors caused by recent save
for err in new_errs:
if err not in ex_errs:
logger.error("Unable to save file - "
"%s" % err[13:len(err)-6])
logger.error("Attempted Save Notes")
logger.error(self.save_notes)
logging.error(
"Unable to save file - %s", err[13:len(err) - 6])
logging.error("Attempted Save Notes")
logging.error(self.save_notes)
# Erase Save Notes
self.save_notes = ""
return False
@ -117,7 +117,7 @@ class AugeasConfigurator(configurator.Configurator):
valid, message = self.check_tempfile_saves(save_files)
if not valid:
logger.fatal(message)
logging.fatal(message)
# What is the protocol in this situation?
# This shouldn't happen if the challenge codebase is correct
return False
@ -153,8 +153,8 @@ class AugeasConfigurator(configurator.Configurator):
result = self._recover_checkpoint(self.direc["temp"])
if result != 0:
# We have a partial or incomplete recovery
logger.fatal("Incomplete or failed recovery for "
"%s" % self.direc["temp"])
logging.fatal("Incomplete or failed recovery for %s",
self.direc["temp"])
sys.exit(67)
# Remember to reload Augeas
self.aug.load()
@ -168,24 +168,24 @@ class AugeasConfigurator(configurator.Configurator):
try:
rollback = int(rollback)
except ValueError:
logger.error("Rollback argument must be a positive integer")
logging.error("Rollback argument must be a positive integer")
# Sanity check input
if rollback < 1:
logger.error("Rollback argument must be a positive integer")
logging.error("Rollback argument must be a positive integer")
return
backups = os.listdir(self.direc["backup"])
backups.sort()
if len(backups) < rollback:
logger.error(("Unable to rollback %d checkpoints, only "
"%d exist") % (rollback, len(backups)))
logging.error("Unable to rollback %d checkpoints, only %d exist",
rollback, len(backups))
while rollback > 0 and backups:
cp_dir = self.direc["backup"] + backups.pop()
result = self._recover_checkpoint(cp_dir)
if result != 0:
logger.fatal("Failed to load checkpoint during rollback")
logging.fatal("Failed to load checkpoint during rollback")
sys.exit(39)
rollback -= 1
@ -262,7 +262,7 @@ class AugeasConfigurator(configurator.Configurator):
if filename not in existing_filepaths:
# Tag files with index so multiple files can
# have the same filename
logger.debug("Creating backup of %s" % filename)
logging.debug("Creating backup of %s", filename)
shutil.copy2(filename, os.path.join(
cp_dir, os.path.basename(filename) + "_" + str(idx)))
op_fd.write(filename + '\n')
@ -295,7 +295,7 @@ class AugeasConfigurator(configurator.Configurator):
path)
except (IOError, OSError):
# This file is required in all checkpoints.
logger.error("Unable to recover files from %s" % cp_dir)
logging.error("Unable to recover files from %s", cp_dir)
return 1
# Remove any newly added files if they exist
@ -304,7 +304,7 @@ class AugeasConfigurator(configurator.Configurator):
try:
shutil.rmtree(cp_dir)
except OSError:
logger.error("Unable to remove directory: %s" % cp_dir)
logging.error("Unable to remove directory: %s", cp_dir)
return -1
return 0
@ -354,7 +354,7 @@ class AugeasConfigurator(configurator.Configurator):
for file_path in files:
new_fd.write("%s\n" % file_path)
except (IOError, OSError):
logger.error("ERROR: Unable to register file creation")
logging.error("ERROR: Unable to register file creation")
def recovery_routine(self):
"""Revert all previously modified files.
@ -373,8 +373,8 @@ class AugeasConfigurator(configurator.Configurator):
# We have a partial or incomplete recovery
# Not as egregious
# TODO: Additional tests? recovery
logger.fatal("Incomplete or failed recovery for %s" %
self.direc["progress"])
logging.fatal("Incomplete or failed recovery for %s",
self.direc["progress"])
sys.exit(68)
# Need to reload configuration after these changes take effect
@ -403,13 +403,12 @@ class AugeasConfigurator(configurator.Configurator):
if os.path.lexists(path):
os.remove(path)
else:
logger.warn((
logging.warn(
"File: %s - Could not be found to be deleted\n"
"Program was probably shut down unexpectedly, "
"in which case this is not a problem") % path)
"Program was probably shut down unexpectedly, ")
except (IOError, OSError):
logger.fatal(
"Unable to remove filepaths contained within %s" % file_list)
logging.fatal(
"Unable to remove filepaths contained within %s", file_list)
sys.exit(41)
return True
@ -441,12 +440,12 @@ class AugeasConfigurator(configurator.Configurator):
shutil.move(changes_since_tmp_path, changes_since_path)
except (IOError, OSError):
logger.error("Unable to finalize checkpoint - adding title")
logging.error("Unable to finalize checkpoint - adding title")
return False
try:
os.rename(cp_dir, final_dir)
except OSError:
logger.error("Unable to finalize checkpoint, %s -> %s" %
(cp_dir, final_dir))
logging.error(
"Unable to finalize checkpoint, %s -> %s", cp_dir, final_dir)
return False
return True

View file

@ -1,8 +1,8 @@
"""ACME challenge."""
import logging
import sys
from letsencrypt.client import CONFIG
from letsencrypt.client import logger
class Challenge(object):
@ -93,8 +93,8 @@ def _find_smart_path(challenges, combos):
combo_total = 0
if not best_combo:
logger.fatal("Client does not support any combination of "
"challenges to satisfy ACME server")
logging.fatal("Client does not support any combination of "
"challenges to satisfy ACME server")
sys.exit(22)
return best_combo

View file

@ -2,6 +2,7 @@
import collections
import csv
import json
import logging
import os
import shutil
import socket
@ -21,7 +22,6 @@ from letsencrypt.client import crypto_util
from letsencrypt.client import display
from letsencrypt.client import errors
from letsencrypt.client import le_util
from letsencrypt.client import logger
# it's weird to point to chocolate servers via raw IPv6 addresses, and
@ -65,9 +65,6 @@ class Client(object):
self.privkey = privkey
self._validate_csr_key_cli() # TODO: catch exceptions
# Logger needs to be initialized before Configurator
self.init_logger()
# TODO: Can probably figure out which configurator to use
# without special packaging based on system info Command
# line arg or client function to discover
@ -178,8 +175,8 @@ class Client(object):
try:
return self.is_expected_msg(auth_dict, "authorization")
except:
logger.fatal("Failed Authorization procedure - "
"cleaning up challenges")
logging.fatal(
"Failed Authorization procedure - cleaning up challenges")
sys.exit(1)
finally:
self.cleanup_challenges(chal_objs)
@ -193,7 +190,7 @@ class Client(object):
:rtype: dict
"""
logger.info("Preparing and sending CSR..")
logging.info("Preparing and sending CSR...")
return self.send_and_receive_expected(
acme.certificate_request(csr_der, self.privkey.pem), "certificate")
@ -303,24 +300,23 @@ class Client(object):
return response
elif response["type"] == "error":
logger.error("%s: %s - More Info: %s" %
(response["error"],
response.get("message", ""),
response.get("moreInfo", "")))
logging.error(
"%s: %s - More Info: %s", response["error"],
response.get("message", ""), response.get("moreInfo", ""))
raise errors.LetsEncryptClientError(response["error"])
elif response["type"] == "defer":
logger.info("Waiting for %d seconds..." % delay)
logging.info("Waiting for %d seconds...", delay)
time.sleep(delay)
response = self.send(acme.status_request(response["token"]))
else:
logger.fatal("Received unexpected message")
logger.fatal("Expected: %s" % expected)
logger.fatal("Received: " + response)
logging.fatal("Received unexpected message")
logging.fatal("Expected: %s" % expected)
logging.fatal("Received: " + response)
sys.exit(33)
logger.error("Server has deferred past the max of %d seconds" %
(rounds * delay))
logging.error(
"Server has deferred past the max of %d seconds", rounds * delay)
def list_certs_keys(self):
"""List trusted Let's Encrypt certificates."""
@ -328,7 +324,7 @@ class Client(object):
certs = []
if not os.path.isfile(list_file):
logger.info(
logging.info(
"You don't have any certificates saved from letsencrypt")
return
@ -399,8 +395,8 @@ class Client(object):
cert_fd.write(
crypto_util.b64_cert_to_pem(certificate_dict["certificate"]))
cert_fd.close()
logger.info("Server issued certificate; certificate written to %s" %
cert_file)
logging.info(
"Server issued certificate; certificate written to %s", cert_file)
if certificate_dict.get("chain", None):
chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 0o644)
@ -408,7 +404,7 @@ class Client(object):
chain_fd.write(crypto_util.b64_cert_to_pem(cert))
chain_fd.close()
logger.info("Cert chain written to %s" % chain_fn)
logging.info("Cert chain written to %s", chain_fn)
# This expects a valid chain file
cert_chain_abspath = os.path.abspath(chain_fn)
@ -420,7 +416,7 @@ class Client(object):
cert_chain_abspath)
# Enable any vhost that was issued to, but not enabled
if not host.enabled:
logger.info("Enabling Site " + host.filep)
logging.info("Enabling Site %s", host.filep)
self.config.enable_site(host)
# sites may have been enabled / final cleanup
@ -464,7 +460,7 @@ class Client(object):
:param dict challenges: challenges from a challenge message
"""
logger.info("Cleaning up challenges...")
logging.info("Cleaning up challenges...")
for chall in challenges:
if chall["type"] in CONFIG.CONFIG_CHALLENGES:
self.config.cleanup()
@ -484,7 +480,7 @@ class Client(object):
path = challenge.gen_challenge_path(
challenge_msg["challenges"], challenge_msg.get("combinations", []))
logger.info("Performing the following challenges:")
logging.info("Performing the following challenges:")
# Every indices element is a list of integers referring to which
# challenges in the master list the challenge object satisfies
@ -507,8 +503,8 @@ class Client(object):
for index in indices[i]:
responses[index] = response
logger.info("Configured Apache for challenges; " +
"waiting for verification...")
logging.info(
"Configured Apache for challenges; waiting for verification...")
return responses, challenge_objs
@ -528,9 +524,10 @@ class Client(object):
idx = 0
if encrypt:
logger.error("Unfortunately securely storing the certificates/"
"keys is not yet available. Stay tuned for the "
"next update!")
logging.error(
"Unfortunately securely storing the certificates/"
"keys is not yet available. Stay tuned for the "
"next update!")
return False
if os.path.isfile(list_file):
@ -566,9 +563,8 @@ class Client(object):
"""
for ssl_vh in vhost:
success, redirect_vhost = self.config.enable_redirect(ssl_vh)
# pylint: disable=maybe-no-member
logger.info("\nRedirect vhost: " + redirect_vhost.filep +
" - " + str(success))
logging.info(
"\nRedirect vhost: %s - %s ", redirect_vhost.filep, success)
# If successful, make sure redirect site is enabled
if success:
self.config.enable_site(redirect_vhost)
@ -615,20 +611,20 @@ class Client(object):
chall = challenges[index]
if chall["type"] == "dvsni":
logger.info(" DVSNI challenge for name %s." % name)
logging.info(" DVSNI challenge for name %s.", name)
sni_satisfies.append(index)
sni_todo.append((str(name), str(chall["r"]),
str(chall["nonce"])))
elif chall["type"] == "recoveryToken":
logger.info("\tRecovery Token Challenge for name: %s." % name)
logging.info("\tRecovery Token Challenge for name: %s.", name)
challenge_obj_indices.append(index)
challenge_objs.append({
type: "recoveryToken",
})
else:
logger.fatal("Challenge not currently supported")
logging.fatal("Challenge not currently supported")
sys.exit(82)
if sni_todo:
@ -640,7 +636,7 @@ class Client(object):
"dvsni_key": self.privkey,
})
challenge_obj_indices.append(sni_satisfies)
logger.debug(sni_todo)
logging.debug(sni_todo)
return challenge_objs, challenge_obj_indices
@ -663,7 +659,7 @@ class Client(object):
key_f.write(key_pem)
key_f.close()
logger.info("Generating key: %s" % key_filename)
logging.info("Generating key: %s", key_filename)
self.privkey = Client.Key(key_filename, key_pem)
@ -678,7 +674,7 @@ class Client(object):
csr_f.write(csr_pem)
csr_f.close()
logger.info("Creating CSR: %s" % csr_filename)
logging.info("Creating CSR: %s", csr_filename)
self.csr = Client.CSR(csr_filename, csr_der, "der")
elif self.csr.type != "der":
@ -726,22 +722,14 @@ class Client(object):
sanity_check_names(names)
if not names:
logger.fatal("No domain names were found in your apache config")
logger.fatal("Either specify which names you would like "
"letsencrypt to validate or add server names "
"to your virtual hosts")
logging.fatal("No domain names were found in your apache config")
logging.fatal("Either specify which names you would like "
"letsencrypt to validate or add server names "
"to your virtual hosts")
sys.exit(1)
return names
def init_logger(self):
if self.use_curses:
logger.setLogger(logger.NcursesLogger())
logger.setLogLevel(logger.INFO)
else:
logger.setLogger(logger.FileLogger(sys.stdout))
logger.setLogLevel(logger.INFO)
def remove_cert_key(cert):
"""Remove certificate key.
@ -778,7 +766,7 @@ def sanity_check_names(names):
"""
for name in names:
if not is_hostname_sane(name):
logger.fatal(repr(name) + " is an impossible hostname")
logging.fatal("%r is an impossible hostname", name)
sys.exit(81)

View file

@ -1,6 +1,7 @@
"""Let's Encrypt client crypto utility functions"""
import binascii
import hashlib
import logging
import time
from Crypto import Random
@ -12,7 +13,6 @@ import M2Crypto
from letsencrypt.client import CONFIG
from letsencrypt.client import le_util
from letsencrypt.client import logger
# TODO: All of these functions need unit tests
@ -53,7 +53,7 @@ def create_sig(msg, key_str, nonce=None, nonce_len=CONFIG.NONCE_SIZE):
hashed = Crypto.Hash.SHA256.new(msg_with_nonce)
signature = Crypto.Signature.PKCS1_v1_5.new(key).sign(hashed)
logger.debug('%s signed as %s' % (msg_with_nonce, signature))
logging.debug('%s signed as %s', msg_with_nonce, signature)
n_bytes = binascii.unhexlify(leading_zeros(hex(key.n)[2:].rstrip("L")))
e_bytes = binascii.unhexlify(leading_zeros(hex(key.e)[2:].rstrip("L")))

64
letsencrypt/client/log.py Normal file
View file

@ -0,0 +1,64 @@
"""Logging utilities."""
import logging
import dialog
from letsencrypt.client import display
class DialogHandler(logging.Handler):
"""Logging handler using dialog info box.
:ivar int height: Height of the info box (without padding).
:ivar int width: Width of the info box (without padding).
:ivar list lines: Lines to be displayed in the info box.
:ivar d: Instance of :class:`dialog.Dialog`.
"""
PADDING_HEIGHT = 2
PADDING_WIDTH = 4
def __init__(self, level=logging.NOTSET, height=display.HEIGHT,
width=display.WIDTH - 4, d=None):
# Handler not new-style -> no super
logging.Handler.__init__(self, level)
self.height = height
self.width = width
# "dialog" collides with module name... pylint: disable=invalid-name
self.d = dialog.Dialog() if d is None else d
self.lines = []
def emit(self, record):
"""Emit message to a dialog info box.
Only show the last (self.height) lines; note that lines can wrap
at self.width, so a single line could actually be multiple
lines.
"""
for line in (record.msg % record.args).splitlines():
# check for lines that would wrap
cur_out = line
while len(cur_out) > self.width:
# find first space before self.width chars into cur_out
last_space_pos = cur_out.rfind(' ', 0, self.width)
if last_space_pos == -1:
# no spaces, just cut them off at whatever
self.lines.append(cur_out[0:self.width])
cur_out = cur_out[self.width:]
else:
# cut off at last space
self.lines.append(cur_out[0:last_space_pos])
cur_out = cur_out[last_space_pos + 1:]
if cur_out != '':
self.lines.append(cur_out)
# show last 16 lines
content = '\n'.join(self.lines[-self.height:])
# add the padding around the box
self.d.infobox(
content, self.height + self.PADDING_HEIGHT,
self.width + self.PADDING_WIDTH)

View file

@ -1,189 +0,0 @@
"""Logger."""
import logger
import textwrap
import time
import dialog
from letsencrypt.client import display
from letsencrypt.client import errors
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(
Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
# log levels
TRACE = 5
DEBUG = 4
INFO = 3
WARN = 2
ERROR = 1
FATAL = 0
NONE = -1
class Logger(Singleton):
debugLevelStr = {TRACE: 'TRACE', DEBUG: 'DEBUG', INFO: 'INFO',
WARN: 'WARN', ERROR: 'ERROR', FATAL: 'FATAL'}
def __init__(self):
self.level = INFO
def debugLevel(self, level=DEBUG):
return self.debugLevelStr[level]
def log(self, level, data):
raise errors.LetsEncryptClientError("No Logger defined")
def timefmt(self, t=None):
if t is None:
t = time.time()
return time.strftime("%b %d %Y %H:%M:%S",
time.localtime(t)) + ('%.03f' % (t - int(t)))[1:]
class FileLogger(Logger):
def __init__(self, outfile):
self.outfile = outfile
def log(self, level, data):
msg = "%s [%s] %s" % (self.timefmt(), self.debugLevel(level), data)
wm = textwrap.fill(msg, 80)
self.outfile.write("%s\n" % wm)
class NcursesLogger(Logger):
def __init__(self,
firstmessage="",
height=display.HEIGHT,
width=display.WIDTH-4):
self.lines = []
self.all_content = ""
self.d = dialog.Dialog()
self.height = height
self.width = width
self.add(firstmessage)
'''
Only show the last (self.height) lines;
note that lines can wrap at self.width, so
a single line could actually be multiple lines
'''
def add(self, s):
self.all_content += s
for line in s.splitlines():
# check for lines that would wrap
cur_out = line
while len(cur_out) > self.width:
# find first space before self.width chars into cur_out
last_space_pos = cur_out.rfind(' ', 0, self.width)
if (last_space_pos == -1):
# no spaces, just cut them off at whatever
self.lines.append(cur_out[0:self.width])
cur_out = cur_out[self.width:]
else:
# cut off at last space
self.lines.append(cur_out[0:last_space_pos])
cur_out = cur_out[last_space_pos+1:]
if cur_out != '':
self.lines.append(cur_out)
# show last 16 lines
self.content = '\n'.join(self.lines[-self.height:])
self.show()
def show(self):
# add the padding around the box
self.d.infobox(self.content, self.height+2, self.width+4)
def log(self, level, data):
self.add(str(data) + "\n")
log_instance = None
def setLogger(log_inst):
global log_instance
log_instance = log_inst
def setLogLevel(log_level):
global log_instance
log_instance.level = log_level
def log(level, data):
global log_instance
if level <= log_instance.level:
log_instance.log(level, data)
def trace(data):
log(TRACE, data)
def debug(data):
log(DEBUG, data)
def info(data):
log(INFO, data)
def warn(data):
log(WARN, data)
def error(data):
log(ERROR, data)
def fatal(data):
log(FATAL, data)
def none(data):
# Uh...what?
pass
if __name__ == "__main__":
# Unit test/example usage:
# Set the logging type you want to use (stdout logging):
# logger.setLogger(FileLogger(sys.stdout))
setLogger(NcursesLogger())
# Set the most verbose you want to log
# (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE)
setLogLevel(logger.TRACE)
# Log a message:
# logger.log(logger.INFO, "logger!")
time.sleep(0.01)
info(("This is a long line, it's pretty long, butitalso hasbig wordsthat "
"areprobably hardtobreak oninan easywayforthe ncurseslib, "
"sowhatdoes itdo then?"))
info("aa " + "a"*70 + "B")
for i in range(20):
info("iteration #%d/20" % i)
time.sleep(0.3)
# Alternatively, use
error("errrrr")
trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows'))

View file

@ -66,7 +66,7 @@ class NginxConfigurator(augeas_configurator.AugeasConfigurator):
# # validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
# # matchObj = validChars.match(arg)
# # if matchObj.group() != arg:
# # logger.error("Error: Invalid regexp characters in %s" % arg)
# # logging.error("Error: Invalid regexp characters in %s", arg)
# # return []
# # Standardize the include argument based on server root

View file

@ -13,7 +13,6 @@ from letsencrypt.client import apache_configurator
from letsencrypt.client import CONFIG
from letsencrypt.client import display
from letsencrypt.client import errors
from letsencrypt.client import logger
# pylint: disable=no-member
UBUNTU_CONFIGS = pkg_resources.resource_filename(
@ -30,8 +29,6 @@ def setUpModule():
global TEMP_DIR, CONFIG_DIR, WORK_DIR
logger.setLogger(logger.FileLogger(sys.stdout))
logger.setLogLevel(logger.INFO)
display.set_display(display.NcursesDisplay())
TEMP_DIR = tempfile.mkdtemp("temp")

View file

@ -0,0 +1,43 @@
"""Tests for letsencrypt.client.log."""
import unittest
import mock
class DialogHandlerTest(unittest.TestCase):
def setUp(self):
self.d = mock.MagicMock() # pylint: disable=invalid-name
from letsencrypt.client.log import DialogHandler
self.handler = DialogHandler(height=2, width=6, d=self.d)
self.handler.PADDING_HEIGHT = 2
self.handler.PADDING_WIDTH = 4
def test_adds_padding(self):
self.handler.emit(mock.MagicMock())
self.d.infobox.assert_called_once_with(mock.ANY, 4, 10)
def test_args_in_msg_get_replaced(self):
assert len('123456') <= self.handler.width
self.handler.emit(mock.MagicMock(msg='123%s', args=(456,)))
self.d.infobox.assert_called_once_with('123456', mock.ANY, mock.ANY)
def test_wraps_nospace_is_greedy(self):
assert len('1234567') > self.handler.width
self.handler.emit(mock.MagicMock(msg='1234%s', args=(567,)))
self.d.infobox.assert_called_once_with('123456\n7', mock.ANY, mock.ANY)
def test_wraps_at_whitespace(self):
assert len('123 567') > self.handler.width
self.handler.emit(mock.MagicMock(msg='123 %s', args=(567,)))
self.d.infobox.assert_called_once_with('123\n567', mock.ANY, mock.ANY)
def test_only_last_lines_are_printed(self):
assert len('a\nb\nc'.split()) > self.handler.height
self.handler.emit(mock.MagicMock(msg='a\n\nb\nc'))
self.d.infobox.assert_called_once_with('b\nc', mock.ANY, mock.ANY)
if __name__ == '__main__':
unittest.main()

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python
"""Parse command line and call the appropriate functions."""
import argparse
import logging
import os
import sys
@ -8,10 +9,7 @@ from letsencrypt.client import apache_configurator
from letsencrypt.client import CONFIG
from letsencrypt.client import client
from letsencrypt.client import display
from letsencrypt.client import logger
logger.setLogger(logger.FileLogger(sys.stdout))
logger.setLogLevel(logger.INFO)
from letsencrypt.client import log
def main():
@ -55,20 +53,26 @@ def main():
"HTTP and HTTPS.")
parser.add_argument("-e", "--agree-eula", dest="eula", action="store_true",
help="Skip the end user license agreement screen.")
parser.add_argument("-t", "--text", dest="curses", action="store_false",
parser.add_argument("-t", "--text", dest="use_curses", action="store_false",
help="Use the text output instead of the curses UI.")
parser.add_argument("--test", dest="test", action="store_true",
help="Run in test mode.")
args = parser.parse_args()
# Set up logging
logger = logging.getLogger()
logger.setLevel(logging.INFO) # TODO: --log
if args.use_curses:
logger.addHandler(log.DialogHandler())
# Enforce '--privkey' is set along with '--csr'.
if args.csr and not args.privkey:
parser.error("private key file (--privkey) must be specified along{0} "
"with the certificate signing request file (--csr)"
.format(os.linesep))
if args.curses:
if args.use_curses:
display.set_display(display.NcursesDisplay())
else:
display.set_display(display.FileDisplay(sys.stdout))
@ -93,7 +97,7 @@ def main():
else:
csr = client.Client.CSR(args.csr[0], args.csr[1], "pem")
acme = client.Client(server, csr, privkey, args.curses)
acme = client.Client(server, csr, privkey, args.use_curses)
if args.revoke:
acme.list_certs_keys()
else: