Repository: certbot/certbot

Issue:
--cert-name causes explosions when trying to use "run" as an installer
One thing you can do with the `run` verb is try to install an existing cert (since the `install` verb is more like a construction kit than a real Certbot subcommand). 
```
certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Found the following certs:
  Certificate Name: isnot.org
    Domains: isnot.org goxogle.com is.isnot.org www.isnot.org
    Expiry Date: 2017-03-20 17:57:00+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/isnot.org/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/isnot.org/privkey.pem
...

certbot --cert-name isnot.org --debug
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Cert not yet due for renewal

You have an existing certificate that has exactly the same domains or certificate name you requested and isn't close to expiry.
(ref: /etc/letsencrypt/renewal/isnot.org.conf)

What would you like to do?
-------------------------------------------------------------------------------
1: Attempt to reinstall this existing certificate
2: Renew & replace the cert (limit ~5 per 7 days)
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 1
Keeping the existing certificate
Traceback (most recent call last):
  File "venv/bin/certbot", line 11, in <module>
    load_entry_point('certbot', 'console_scripts', 'certbot')()
  File "/home/ubuntu/letsencrypt/certbot/main.py", line 831, in main
    return config.func(config, plugins)
  File "/home/ubuntu/letsencrypt/certbot/main.py", line 570, in run
    lineage.chain, lineage.fullchain)
  File "/home/ubuntu/letsencrypt/certbot/client.py", line 380, in deploy_certificate
    for dom in domains:
TypeError: 'NoneType' object is not iterable
```


Discussion:

Resolving patch:
diff --git a/certbot/main.py b/certbot/main.py
--- a/certbot/main.py
+++ b/certbot/main.py
@@ -284,17 +284,26 @@ def _find_domains_or_certname(config, installer):
     """Retrieve domains and certname from config or user input.
     """
     domains = None
+    certname = config.certname
+    # first, try to get domains from the config
     if config.domains:
         domains = config.domains
-    elif not config.certname:
+    # if we can't do that but we have a certname, get the domains
+    # with that certname
+    elif certname:
+        domains = cert_manager.domains_for_certname(config, certname)
+
+    # that certname might not have existed, or there was a problem.
+    # try to get domains from the user.
+    if not domains:
         domains = display_ops.choose_names(installer)
 
-    if not domains and not config.certname:
+    if not domains and not certname:
         raise errors.Error("Please specify --domains, or --installer that "
                            "will help in domain names autodiscovery, or "
                            "--cert-name for an existing certificate name.")
 
-    return domains, config.certname
+    return domains, certname
 
 
 def _report_new_cert(config, cert_path, fullchain_path):


Test patch:
diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py
--- a/certbot/tests/main_test.py
+++ b/certbot/tests/main_test.py
@@ -159,10 +159,12 @@ def test_find_lineage_for_domains_and_certname(self, mock_report_cert,
         self.assertRaises(errors.ConfigurationError, self._call,
             ('certonly --webroot -d example.com -d test.com --cert-name example.com').split())
 
+    @mock.patch('certbot.cert_manager.domains_for_certname')
+    @mock.patch('certbot.display.ops.choose_names')
     @mock.patch('certbot.cert_manager.lineage_for_certname')
     @mock.patch('certbot.main._report_new_cert')
     def test_find_lineage_for_domains_new_certname(self, mock_report_cert,
-        mock_lineage):
+        mock_lineage, mock_choose_names, mock_domains_for_certname):
         mock_lineage.return_value = None
 
         # no lineage with this name but we specified domains so create a new cert
@@ -172,8 +174,10 @@ def test_find_lineage_for_domains_new_certname(self, mock_report_cert,
         self.assertTrue(mock_report_cert.call_count == 1)
 
         # no lineage with this name and we didn't give domains
-        self.assertRaises(errors.ConfigurationError, self._call,
-            ('certonly --webroot --cert-name example.com').split())
+        mock_choose_names.return_value = ["somename"]
+        mock_domains_for_certname.return_value = None
+        self._call(('certonly --webroot --cert-name example.com').split())
+        self.assertTrue(mock_choose_names.called)
 
 class FindDomainsOrCertnameTest(unittest.TestCase):
     """Tests for certbot.main._find_domains_or_certname."""
@@ -193,6 +197,14 @@ def test_no_results(self, mock_choose_names):
         # pylint: disable=protected-access
         self.assertRaises(errors.Error, main._find_domains_or_certname, mock_config, None)
 
+    @mock.patch('certbot.cert_manager.domains_for_certname')
+    def test_grab_domains(self, mock_domains):
+        mock_config = mock.Mock(domains=None, certname="one.com")
+        mock_domains.return_value = ["one.com", "two.com"]
+        # pylint: disable=protected-access
+        self.assertEqual(main._find_domains_or_certname(mock_config, None),
+            (["one.com", "two.com"], "one.com"))
+
 
 class RevokeTest(unittest.TestCase):
     """Tests for certbot.main.revoke."""