Repository: certbot/certbot

Issue:
Can't install new certificates with certbot-apache under Gentoo since version 0.20.0
It seems that version 0.20.0 has a major architecture revision from 0.19.0, in particular regarding OS specific overrides. But the Gentoo overrides have a flaw when trying to install a new certificate : at one point it tries to run "apache2ctl -t -D DUMP_MODULES", which is no good at all under Gentoo because apache2ctl is a dummy script redirecting to /etc/init.d/apache2. It generates this error :

`Error in checking parameter list: /etc/init.d/apache2: invalid option -- 't'`

## My operating system is (include version):
Gentoo

## I installed Certbot with (certbot-auto, OS package manager, pip, etc):
OS package manager (emerge) with testing (~amd64) flag

## I ran this command and it produced this output:
```
certbot -d test.example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for test.example.com
Waiting for verification...
Cleaning up challenges
Error in checking parameter list: /etc/init.d/apache2: invalid option -- 't'

Apache is unable to check whether or not the module is loaded because Apache is misconfigured.
```

## Certbot's behavior differed from what I expected because:
Certbot did not install the certificate because it failed to run apache2ctl -t -D DUMP_MODULES


Discussion:
Pinging @joohoi because this is related to your pull request #5202 
Solution is probably to override the reset_modules in the same way that you override update_runtime_variables, because everything goes well until cleanup when reset_modules is called

Sorry I don't have enough know-how to patch it myself
Thanks for the detailed bug report, you are completely right about the fix too! While I was working on the PR, I actually patched that functionality for Gentoo ( https://bugs.gentoo.org/634414 ) , and looks like I left the patched version of apache2ctl in my test system and this go through. 
I'm going to fix this asap, sorry for the inconvinience.

Resolving patch:
diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/override_gentoo.py
--- a/certbot-apache/certbot_apache/override_gentoo.py
+++ b/certbot-apache/certbot_apache/override_gentoo.py
@@ -49,6 +49,7 @@ def __init__(self, *args, **kwargs):
     def update_runtime_variables(self):
         """ Override for update_runtime_variables for custom parsing """
         self.parse_sysconfig_var()
+        self.update_modules()
 
     def parse_sysconfig_var(self):
         """ Parses Apache CLI options from Gentoo configuration file """
@@ -56,3 +57,10 @@ def parse_sysconfig_var(self):
                                                 "APACHE2_OPTS")
         for k in defines.keys():
             self.variables[k] = defines[k]
+
+    def update_modules(self):
+        """Get loaded modules from httpd process, and add them to DOM"""
+        mod_cmd = [self.configurator.constant("apache_cmd"), "modules"]
+        matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module")
+        for mod in matches:
+            self.add_mod(mod.strip())


Test patch:
diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/certbot_apache/tests/gentoo_test.py
--- a/certbot-apache/certbot_apache/tests/gentoo_test.py
+++ b/certbot-apache/certbot_apache/tests/gentoo_test.py
@@ -2,6 +2,8 @@
 import os
 import unittest
 
+import mock
+
 from certbot_apache import override_gentoo
 from certbot_apache import obj
 from certbot_apache.tests import util
@@ -46,9 +48,10 @@ def setUp(self):  # pylint: disable=arguments-differ
                                                     config_root=config_root,
                                                     vhost_root=vhost_root)
 
-        self.config = util.get_apache_configurator(
-            self.config_path, self.vhost_path, self.config_dir, self.work_dir,
-            os_info="gentoo")
+        with mock.patch("certbot_apache.override_gentoo.GentooParser.update_runtime_variables"):
+            self.config = util.get_apache_configurator(
+                self.config_path, self.vhost_path, self.config_dir, self.work_dir,
+                os_info="gentoo")
         self.vh_truth = get_vh_truth(
             self.temp_dir, "gentoo_apache/apache")
 
@@ -78,9 +81,47 @@ def test_get_sysconfig_vars(self):
         self.config.parser.apacheconfig_filep = os.path.realpath(
             os.path.join(self.config.parser.root, "../conf.d/apache2"))
         self.config.parser.variables = {}
-        self.config.parser.update_runtime_variables()
+        with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"):
+            self.config.parser.update_runtime_variables()
         for define in defines:
             self.assertTrue(define in self.config.parser.variables.keys())
 
+    @mock.patch("certbot_apache.parser.ApacheParser.parse_from_subprocess")
+    def test_no_binary_configdump(self, mock_subprocess):
+        """Make sure we don't call binary dumps other than modules from Apache
+        as this is not supported in Gentoo currently"""
+
+        with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"):
+            self.config.parser.update_runtime_variables()
+            self.config.parser.reset_modules()
+        self.assertFalse(mock_subprocess.called)
+
+        self.config.parser.update_runtime_variables()
+        self.config.parser.reset_modules()
+        self.assertTrue(mock_subprocess.called)
+
+    @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
+    def test_opportunistic_httpd_runtime_parsing(self, mock_get):
+        mod_val = (
+            'Loaded Modules:\n'
+            ' mock_module (static)\n'
+            ' another_module (static)\n'
+        )
+        def mock_get_cfg(command):
+            """Mock httpd process stdout"""
+            if command == ['apache2ctl', 'modules']:
+                return mod_val
+        mock_get.side_effect = mock_get_cfg
+        self.config.parser.modules = set()
+
+        with mock.patch("certbot.util.get_os_info") as mock_osi:
+            # Make sure we have the have the CentOS httpd constants
+            mock_osi.return_value = ("gentoo", "123")
+            self.config.parser.update_runtime_variables()
+
+        self.assertEquals(mock_get.call_count, 1)
+        self.assertEquals(len(self.config.parser.modules), 4)
+        self.assertTrue("mod_another.c" in self.config.parser.modules)
+
 if __name__ == "__main__":
     unittest.main()  # pragma: no cover