Repository: certbot/certbot

Issue:
Apache listed as misconfigured when not installed
With freshly install Let's Encrypt on freshly installed Debian, I experienced the following:

```
letsencrypt -t -vvv --debug --agree-tos --agree-eula auth
2015-09-30 15:08:28,497:DEBUG:letsencrypt.cli:Root logging level set at 0
2015-09-30 15:08:28,498:INFO:letsencrypt.cli:Saving debug log to /var/log/letsencrypt/letsencrypt.log
2015-09-30 15:08:28,498:DEBUG:letsencrypt.cli:Arguments: ['-t', '-vvv', '--debug', '--agree-tos', '--agree-eula', 'auth']
2015-09-30 15:08:28,499:DEBUG:letsencrypt.cli:Discovered plugins: PluginsRegistry(PluginEntryPoint#apache,PluginEntryPoint#nginx,PluginEntryPoint#null,PluginEntryPoint#manual,PluginEntryPoint#standalone)
2015-09-30 15:08:28,608:ERROR:letsencrypt.le_util:Unable to run the command: apache2ctl configtest
2015-09-30 15:08:28,609:DEBUG:letsencrypt.plugins.disco:Misconfigured PluginEntryPoint#apache: Unable to run the command: apache2ctl configtest
Traceback (most recent call last):
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt/plugins/disco.py", line 103, in prepare
    self._initialized.prepare()
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt_apache/configurator.py", line 141, in prepare
    self.config_test()
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt_apache/configurator.py", line 1068, in config_test
    raise errors.MisconfigurationError(str(err))
MisconfigurationError: Unable to run the command: apache2ctl configtest
2015-09-30 15:08:28,609:DEBUG:letsencrypt.plugins.disco:No installation (PluginEntryPoint#nginx): Could not find configuration root
Traceback (most recent call last):
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt/plugins/disco.py", line 103, in prepare
    self._initialized.prepare()
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt_nginx/configurator.py", line 111, in prepare
    self.conf('server-root'), self.mod_ssl_conf)
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt_nginx/parser.py", line 29, in __init__
    self.loc = self._set_locations(ssl_options)
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt_nginx/parser.py", line 179, in _set_locations
    root = self._find_config_root()
  File "/root/le/venv/local/lib/python2.7/site-packages/letsencrypt_nginx/parser.py", line 202, in _find_config_root
    "Could not find configuration root")
NoInstallationError: Could not find configuration root
2015-09-30 15:08:28,610:DEBUG:letsencrypt.display.ops:Multiple candidate plugins: * apache
Description: Apache Web Server - Alpha
Interfaces: IAuthenticator, IInstaller, IPlugin
Entry point: apache = letsencrypt_apache.configurator:ApacheConfigurator
Initialized: <letsencrypt_apache.configurator.ApacheConfigurator object at 0x7f698b309110>
Prep: Unable to run the command: apache2ctl configtest

* manual
Description: Manual Authenticator
Interfaces: IAuthenticator, IPlugin
Entry point: manual = letsencrypt.plugins.manual:Authenticator
Initialized: <letsencrypt.plugins.manual.Authenticator object at 0x7f698b2eff90>
Prep: True

* standalone
Description: Standalone Authenticator
Interfaces: IAuthenticator, IPlugin
Entry point: standalone = letsencrypt.plugins.standalone.authenticator:StandaloneAuthenticator
Initialized: <letsencrypt.plugins.standalone.authenticator.StandaloneAuthenticator object at 0x7f698b2efcd0>
Prep: True

How would you like to authenticate with the Let's Encrypt CA?
-------------------------------------------------------------------------------
1: Apache Web Server - Alpha (apache) [Misconfigured]
2: Manual Authenticator (manual)
3: Standalone Authenticator (standalone)
-------------------------------------------------------------------------------
Select the appropriate number [1-3] then [enter] (press 'c' to cancel): 
```

The Apache plugin should raise `NoInstallationError` to prevent itself from being listed (as misconfigured) in prompts to the user when not installed.



Discussion:
Thanks!  

In the prepare() method... each of the executables should be verified to exist...

There is a function for this in letsencrypt/le_util.py
le_util.exe_exists(apache_ctl)

if not raise NoInstallationError


Resolving patch:
diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py
--- a/letsencrypt-apache/letsencrypt_apache/configurator.py
+++ b/letsencrypt-apache/letsencrypt_apache/configurator.py
@@ -137,6 +137,12 @@ def prepare(self):
         :raises .errors.PluginError: If there is any other error
 
         """
+        # Verify Apache is installed
+        for exe in (self.conf("ctl"), self.conf("enmod"),
+                    self.conf("dismod"), self.conf("init-script")):
+            if not le_util.exe_exists(exe):
+                raise errors.NoInstallationError
+
         # Make sure configuration is valid
         self.config_test()
 


Test patch:
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
--- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
+++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
@@ -37,8 +37,16 @@ def tearDown(self):
         shutil.rmtree(self.config_dir)
         shutil.rmtree(self.work_dir)
 
+    @mock.patch("letsencrypt_apache.configurator.le_util.exe_exists")
+    def test_prepare_no_install(self, mock_exe_exists):
+        mock_exe_exists.return_value = False
+        self.assertRaises(
+            errors.NoInstallationError, self.config.prepare)
+
     @mock.patch("letsencrypt_apache.parser.ApacheParser")
-    def test_prepare_version(self, _):
+    @mock.patch("letsencrypt_apache.configurator.le_util.exe_exists")
+    def test_prepare_version(self, mock_exe_exists, _):
+        mock_exe_exists.return_value = True
         self.config.version = None
         self.config.config_test = mock.Mock()
         self.config.get_version = mock.Mock(return_value=(1, 1))
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py
--- a/letsencrypt-apache/letsencrypt_apache/tests/util.py
+++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py
@@ -66,31 +66,34 @@ def get_apache_configurator(
 
     """
     backups = os.path.join(work_dir, "backups")
+    mock_le_config = mock.MagicMock(
+        apache_server_root=config_path,
+        apache_le_vhost_ext=constants.CLI_DEFAULTS["le_vhost_ext"],
+        backup_dir=backups,
+        config_dir=config_dir,
+        temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
+        in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
+        work_dir=work_dir)
 
     with mock.patch("letsencrypt_apache.configurator."
                     "subprocess.Popen") as mock_popen:
-        with mock.patch("letsencrypt_apache.parser.ApacheParser."
-                        "update_runtime_variables"):
-            # This indicates config_test passes
-            mock_popen().communicate.return_value = ("Fine output", "No problems")
-            mock_popen().returncode = 0
-
-            config = configurator.ApacheConfigurator(
-                config=mock.MagicMock(
-                    apache_server_root=config_path,
-                    apache_le_vhost_ext=constants.CLI_DEFAULTS["le_vhost_ext"],
-                    backup_dir=backups,
-                    config_dir=config_dir,
-                    temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
-                    in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
-                    work_dir=work_dir),
-                name="apache",
-                version=version)
-            # This allows testing scripts to set it a bit more quickly
-            if conf is not None:
-                config.conf = conf  # pragma: no cover
-
-            config.prepare()
+        # This indicates config_test passes
+        mock_popen().communicate.return_value = ("Fine output", "No problems")
+        mock_popen().returncode = 0
+        with mock.patch("letsencrypt_apache.configurator.le_util."
+                        "exe_exists") as mock_exe_exists:
+            mock_exe_exists.return_value = True
+            with mock.patch("letsencrypt_apache.parser.ApacheParser."
+                            "update_runtime_variables"):
+                config = configurator.ApacheConfigurator(
+                    config=mock_le_config,
+                    name="apache",
+                    version=version)
+                # This allows testing scripts to set it a bit more quickly
+                if conf is not None:
+                    config.conf = conf  # pragma: no cover
+
+                config.prepare()
 
     return config