def test_noninteractive(self, mock_util): mock_util().menu.side_effect = errors.MissingCommandlineFlag( "no vhost default") try: self._call(self.vhosts) except errors.MissingCommandlineFlag as e: self.assertTrue("VirtualHost directives" in e.message)
def _vhost_menu(domain, vhosts): """Select an appropriate Apache Vhost. :param vhosts: Available Apache Virtual Hosts :type vhosts: :class:`list` of type `~obj.Vhost` :returns: Display tuple - ('code', tag') :rtype: `tuple` """ # Free characters in the line of display text (9 is for ' | ' formatting) free_chars = display_util.WIDTH - len("HTTPS") - len("Enabled") - 9 if free_chars < 2: logger.debug("Display size is too small for " "letsencrypt_apache.display_ops._vhost_menu()") # This runs the edge off the screen, but it doesn't cause an "error" filename_size = 1 disp_name_size = 1 else: # Filename is a bit more important and probably longer with 000-* filename_size = int(free_chars * .6) disp_name_size = free_chars - filename_size choices = [] for vhost in vhosts: if len(vhost.get_names()) == 1: disp_name = next(iter(vhost.get_names())) elif len(vhost.get_names()) == 0: disp_name = "" else: disp_name = "Multiple Names" choices.append("{fn:{fn_size}s} | {name:{name_size}s} | {https:5s} | " "{active:7s}".format( fn=os.path.basename(vhost.filep)[:filename_size], name=disp_name[:disp_name_size], https="HTTPS" if vhost.ssl else "", active="Enabled" if vhost.enabled else "", fn_size=filename_size, name_size=disp_name_size)) try: code, tag = zope.component.getUtility(interfaces.IDisplay).menu( "We were unable to find a vhost with a ServerName " "or Address of {0}.{1}Which virtual host would you " "like to choose?\n(note: conf files with multiple " "vhosts are not yet supported)".format(domain, os.linesep), choices, help_label="More Info", ok_label="Select") except errors.MissingCommandlineFlag as e: msg = ("Failed to run Apache plugin non-interactively{1}{0}{1}" "(The best solution is to add ServerName or ServerAlias " "entries to the VirtualHost directives of your apache " "configuration files.)".format(e, os.linesep)) raise errors.MissingCommandlineFlag(msg) return code, tag
def _interaction_fail(self, message, cli_flag, extra=""): "Error out in case of an attempt to interact in noninteractive mode" msg = "Missing command line flag or config entry for this setting:\n" msg += message if extra: msg += "\n" + extra if cli_flag: msg += "\n\n(You can set this with the {0} flag)".format(cli_flag) raise errors.MissingCommandlineFlag(msg)
def pick_plugin(config, default, plugins, question, ifaces): """Pick plugin. :param letsencrypt.interfaces.IConfig: Configuration :param str default: Plugin name supplied by user or ``None``. :param letsencrypt.plugins.disco.PluginsRegistry plugins: All plugins registered as entry points. :param str question: Question to be presented to the user in case multiple candidates are found. :param list ifaces: Interfaces that plugins must provide. :returns: Initialized plugin. :rtype: IPlugin """ if default is not None: # throw more UX-friendly error if default not in plugins filtered = plugins.filter(lambda p_ep: p_ep.name == default) else: if config.noninteractive_mode: # it's really bad to auto-select the single available plugin in # non-interactive mode, because an update could later add a second # available plugin raise errors.MissingCommandlineFlag( "Missing command line flags. For non-interactive execution, " "you will need to specify a plugin on the command line. Run " "with '--help plugins' to see a list of options, and see " "https://eff.org/letsencrypt-plugins for more detail on what " "the plugins do and how to use them.") filtered = plugins.visible().ifaces(ifaces) filtered.init(config) verified = filtered.verify(ifaces) verified.prepare() prepared = verified.available() if len(prepared) > 1: logger.debug("Multiple candidate plugins: %s", prepared) plugin_ep = choose_plugin(prepared.values(), question) if plugin_ep is None: return None else: return plugin_ep.init() elif len(prepared) == 1: plugin_ep = prepared.values()[0] logger.debug("Single candidate plugin: %s", plugin_ep) if plugin_ep.misconfigured: return None return plugin_ep.init() else: logger.debug("No candidate plugin") return None
def get_email(more=False, invalid=False): """Prompt for valid email address. :param bool more: explain why the email is strongly advisable, but how to skip it :param bool invalid: true if the user just typed something, but it wasn't a valid-looking email :returns: Email or ``None`` if cancelled by user. :rtype: str """ msg = "Enter email address (used for urgent notices and lost key recovery)" if invalid: msg = "There seem to be problems with that address. " + msg if more: msg += ( '\n\nIf you really want to skip this, you can run the client with ' '--register-unsafely-without-email but make sure you backup your ' 'account key from /etc/letsencrypt/accounts\n\n') try: code, email = zope.component.getUtility(interfaces.IDisplay).input(msg) except errors.MissingCommandlineFlag: msg = ( "You should register before running non-interactively, or provide --agree-tos" " and --email <email_address> flags") raise errors.MissingCommandlineFlag(msg) if code == display_util.OK: if le_util.safe_email(email): return email else: # TODO catch the server's ACME invalid email address error, and # make a similar call when that happens return get_email(more=True, invalid=(email != "")) else: return None
def choose_configurator_plugins(config, plugins, verb): """ Figure out which configurator we're going to use, modifies config.authenticator and config.installer strings to reflect that choice if necessary. :raises errors.PluginSelectionError if there was a problem :returns: (an `IAuthenticator` or None, an `IInstaller` or None) :rtype: tuple """ req_auth, req_inst = cli_plugin_requests(config) # Which plugins do we need? if verb == "run": need_inst = need_auth = True from letsencrypt.cli import cli_command if req_auth in noninstaller_plugins and not req_inst: msg = ( 'With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' '{1} {2} certonly --{0}{1}{1}' '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins' '{1} and "--help plugins" for more information.)'.format( req_auth, os.linesep, cli_command)) raise errors.MissingCommandlineFlag(msg) else: need_inst = need_auth = False if verb == "certonly": need_auth = True if verb == "install": need_inst = True if config.authenticator: logger.warn( "Specifying an authenticator doesn't make sense in install mode" ) # Try to meet the user's request and/or ask them to pick plugins authenticator = installer = None if verb == "run" and req_auth == req_inst: # Unless the user has explicitly asked for different auth/install, # only consider offering a single choice authenticator = installer = pick_configurator(config, req_inst, plugins) else: if need_inst or req_inst: installer = pick_installer(config, req_inst, plugins) if need_auth: authenticator = pick_authenticator(config, req_auth, plugins) logger.debug("Selected authenticator %s and installer %s", authenticator, installer) # Report on any failures if need_inst and not installer: diagnose_configurator_problem("installer", req_inst, plugins) if need_auth and not authenticator: diagnose_configurator_problem("authenticator", req_auth, plugins) record_chosen_plugins(config, plugins, authenticator, installer) return installer, authenticator