Repository: certbot/certbot

Issue:
Use account reuse symlink logic when loading an account
To reproduce, start with an empty Certbot config directory and use any released version of Certbot to obtain an ACMEv1 prod cert. You shouldn't set `--server` anywhere.

Then checkout the `acmev2-default` branch and run `certbot renew --force-renewal`. You should see an error like:
```
Traceback (most recent call last):
  File "/home/bmw/src/certbot/certbot/renewal.py", line 430, in handle_renewal_request
    main.renew_cert(lineage_config, plugins, renewal_candidate)
  File "/home/bmw/src/certbot/certbot/main.py", line 1190, in renew_cert
    le_client = _init_le_client(config, auth, installer)
  File "/home/bmw/src/certbot/certbot/main.py", line 641, in _init_le_client
    acc, acme = _determine_account(config)
  File "/home/bmw/src/certbot/certbot/main.py", line 508, in _determine_account
    acc = account_storage.load(config.account)
  File "/home/bmw/src/certbot/certbot/account.py", line 224, in load
    return self._load_for_server_path(account_id, self.config.server_path)
  File "/home/bmw/src/certbot/certbot/account.py", line 204, in _load_for_server_path
    "Account at %s does not exist" % account_dir_path)
AccountNotFound: Account at /etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/8f7e408abe6ff5832f20529fb812abad does not exist
```
This happens because the default server value changed to ACMEv2 and Certbot is looking for the account specified in the renewal configuration file in the ACMEv2 account directory which doesn't exist and hasn't been symlinked yet.


Discussion:
planning to move the account reuse logic into `load`, then have `find_all` use that logic
or maybe also call into the logic

either way, planning to add things to `load`

Resolving patch:
diff --git a/certbot/account.py b/certbot/account.py
--- a/certbot/account.py
+++ b/certbot/account.py
@@ -175,33 +175,43 @@ def _find_all_for_server_path(self, server_path):
             except errors.AccountStorageError:
                 logger.debug("Account loading problem", exc_info=True)
 
-
         if not accounts and server_path in constants.LE_REUSE_SERVERS:
             # find all for the next link down
             prev_server_path = constants.LE_REUSE_SERVERS[server_path]
             prev_accounts = self._find_all_for_server_path(prev_server_path)
             # if we found something, link to that
             if prev_accounts:
-                if os.path.islink(accounts_dir):
-                    os.unlink(accounts_dir)
-                else:
-                    try:
-                        os.rmdir(accounts_dir)
-                    except OSError:
-                        return []
-                prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path)
-                os.symlink(prev_account_dir, accounts_dir)
+                try:
+                    self._symlink_to_accounts_dir(prev_server_path, server_path)
+                except OSError:
+                    return []
             accounts = prev_accounts
         return accounts
 
     def find_all(self):
         return self._find_all_for_server_path(self.config.server_path)
 
+    def _symlink_to_accounts_dir(self, prev_server_path, server_path):
+        accounts_dir = self.config.accounts_dir_for_server_path(server_path)
+        if os.path.islink(accounts_dir):
+            os.unlink(accounts_dir)
+        else:
+            os.rmdir(accounts_dir)
+        prev_account_dir = self.config.accounts_dir_for_server_path(prev_server_path)
+        os.symlink(prev_account_dir, accounts_dir)
+
     def _load_for_server_path(self, account_id, server_path):
         account_dir_path = self._account_dir_path_for_server_path(account_id, server_path)
-        if not os.path.isdir(account_dir_path):
-            raise errors.AccountNotFound(
-                "Account at %s does not exist" % account_dir_path)
+        if not os.path.isdir(account_dir_path): # isdir is also true for symlinks
+            if server_path in constants.LE_REUSE_SERVERS:
+                prev_server_path = constants.LE_REUSE_SERVERS[server_path]
+                prev_loaded_account = self._load_for_server_path(account_id, prev_server_path)
+                # we didn't error so we found something, so create a symlink to that
+                self._symlink_to_accounts_dir(prev_server_path, server_path)
+                return prev_loaded_account
+            else:
+                raise errors.AccountNotFound(
+                    "Account at %s does not exist" % account_dir_path)
 
         try:
             with open(self._regr_path(account_dir_path)) as regr_file:


Test patch:
diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py
--- a/certbot/tests/account_test.py
+++ b/certbot/tests/account_test.py
@@ -241,6 +241,14 @@ def test_corrupted_account(self, mock_rmdir):
         self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')
         self.assertEqual([], self.storage.find_all())
 
+    def test_upgrade_load(self):
+        self._set_server('https://acme-staging.api.letsencrypt.org/directory')
+        self.storage.save(self.acc, self.mock_client)
+        prev_account = self.storage.load(self.acc.id)
+        self._set_server('https://acme-staging-v02.api.letsencrypt.org/directory')
+        account = self.storage.load(self.acc.id)
+        self.assertEqual(prev_account, account)
+
     def test_load_ioerror(self):
         self.storage.save(self.acc, self.mock_client)
         mock_open = mock.mock_open()