diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 256e0c801..7ff47990f 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -600,6 +600,13 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): "regardless of whether it is near expiry. (Often " "--keep-until-expiring is more appropriate). Also implies " "--expand.") + helpful.add( + "automation", "--allow-subset-of-names", action="store_true", + help="When performing domain validation, do not consider it a failure " + "if authorizations can not be obtained for a strict subset of " + "the requested domains. This may be useful for allowing renewals for " + "multiple domains to succeed even if some domains no longer point " + "at this system. This option cannot be used with --csr.") helpful.add( "automation", "--agree-tos", dest="tos", action="store_true", help="Agree to the Let's Encrypt Subscriber Agreement") @@ -617,6 +624,10 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): "automation", "--no-self-upgrade", action="store_true", help="(letsencrypt-auto only) prevent the letsencrypt-auto script from" " upgrading itself to newer released versions") + helpful.add( + "automation", "-q", "--quiet", dest="quiet", action="store_true", + help="Silence all output except errors. Useful for automation via cron." + "Implies --non-interactive.") helpful.add_group( "testing", description="The following flags are meant for " @@ -677,12 +688,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): "security", "--strict-permissions", action="store_true", help="Require that all configuration files are owned by the current " "user; only needed if your config is somewhere unsafe like /tmp/") - helpful.add( - "automation", "--allow-subset-of-names", - action="store_true", - help="When performing domain validation, do not consider it a failure " - "if authorizations can not be obtained for a strict subset of " - "the requested domains. This option cannot be used with --csr.") helpful.add_group( "renew", description="The 'renew' subcommand will attempt to renew all" diff --git a/letsencrypt/main.py b/letsencrypt/main.py index 0afccc85e..1998e09ea 100644 --- a/letsencrypt/main.py +++ b/letsencrypt/main.py @@ -34,7 +34,6 @@ from letsencrypt.display import util as display_util, ops as display_ops from letsencrypt.plugins import disco as plugins_disco from letsencrypt.plugins import selection as plug_sel - logger = logging.getLogger(__name__) @@ -537,20 +536,21 @@ def obtain_cert(config, plugins, lineage=None): domains = _find_domains(config, installer) _, action = _auth_from_domains(le_client, config, domains, lineage) + notify = zope.component.getUtility(interfaces.IDisplay).notification if config.dry_run: _report_successful_dry_run(config) elif config.verb == "renew": if installer is None: # Tell the user that the server was not restarted. - print("new certificate deployed without reload, fullchain is", - lineage.fullchain) + notify("new certificate deployed without reload, fullchain is {0}".format( + lineage.fullchain), pause=False) else: # In case of a renewal, reload server to pick up new certificate. # In principle we could have a configuration option to inhibit this # from happening. installer.restart() - print("new certificate deployed with reload of", - config.installer, "server; fullchain is", lineage.fullchain) + notify("new certificate deployed with reload of {0} server; fullchain is {1}".format( + config.installer, lineage.fullchain), pause=False) _suggest_donation_if_appropriate(config, action) def renew(config, unused_plugins): @@ -689,7 +689,10 @@ def main(cli_args=sys.argv[1:]): sys.excepthook = functools.partial(_handle_exception, config=config) # Displayer - if config.noninteractive_mode: + if config.quiet: + config.noninteractive_mode = True + displayer = display_util.NoninteractiveDisplay(open("/dev/null", "w")) + elif config.noninteractive_mode: displayer = display_util.NoninteractiveDisplay(sys.stdout) elif config.text_mode: displayer = display_util.FileDisplay(sys.stdout) diff --git a/letsencrypt/renewal.py b/letsencrypt/renewal.py index 27546bec9..f0edd55fe 100644 --- a/letsencrypt/renewal.py +++ b/letsencrypt/renewal.py @@ -12,6 +12,7 @@ import zope.component from letsencrypt import configuration from letsencrypt import cli from letsencrypt import errors +from letsencrypt import interfaces from letsencrypt import storage from letsencrypt.plugins import disco as plugins_disco @@ -201,44 +202,53 @@ def should_renew(config, lineage): def _renew_describe_results(config, renew_successes, renew_failures, renew_skipped, parse_failures): - def _status(msgs, category): - return " " + "\n ".join("%s (%s)" % (m, category) for m in msgs) + def notify(msg): + "Our version of print()" + zope.component.getUtility(interfaces.IDisplay).notification(msg, pause=False) + + def report(msgs, category): + "Report results for a category of renewal outcomes" + notify(" " + "\n ".join("%s (%s)" % (m, category) for m in msgs)) + if config.dry_run: - print("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry") - print("** (The test certificates below have not been saved.)") - print() + notify("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry") + notify("** (The test certificates below have not been saved.)") + notify("") if renew_skipped: - print("The following certs are not due for renewal yet:") - print(_status(renew_skipped, "skipped")) + notify("The following certs are not due for renewal yet:") + report(renew_skipped, "skipped") if not renew_successes and not renew_failures: - print("No renewals were attempted.") + notify("No renewals were attempted.") elif renew_successes and not renew_failures: - print("Congratulations, all renewals succeeded. The following certs " - "have been renewed:") - print(_status(renew_successes, "success")) + notify("Congratulations, all renewals succeeded. The following certs " + "have been renewed:") + report(renew_successes, "success") elif renew_failures and not renew_successes: - print("All renewal attempts failed. The following certs could not be " - "renewed:") - print(_status(renew_failures, "failure")) + notify("All renewal attempts failed. The following certs could not be " + "renewed:") + report(renew_failures, "failure") elif renew_failures and renew_successes: - print("The following certs were successfully renewed:") - print(_status(renew_successes, "success")) - print("\nThe following certs could not be renewed:") - print(_status(renew_failures, "failure")) + notify("The following certs were successfully renewed:") + report(renew_successes, "success") + notify("\nThe following certs could not be renewed:") + report(renew_failures, "failure") if parse_failures: - print("\nAdditionally, the following renewal configuration files " - "were invalid: ") - print(_status(parse_failures, "parsefail")) + notify("\nAdditionally, the following renewal configuration files " + "were invalid: ") + report(parse_failures, "parsefail") if config.dry_run: - print("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry") - print("** (The test certificates above have not been saved.)") + notify("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry") + notify("** (The test certificates above have not been saved.)") def renew_all_lineages(config): """Examine each lineage; renew if due and report results""" + def _notify(msg): + zope.component.getUtility(interfaces.IDisplay).notification(msg, pause=False) + if config.domains != []: raise errors.Error("Currently, the renew verb is only capable of " "renewing all installed certificates that are due " @@ -253,7 +263,7 @@ def renew_all_lineages(config): renew_skipped = [] parse_failures = [] for renewal_file in renewal_conf_files(renewer_config): - print("Processing " + renewal_file) + _notify("Processing " + renewal_file) lineage_config = copy.deepcopy(config) # Note that this modifies config (to add back the configuration