summaryrefslogtreecommitdiff
path: root/cloudinit/gpg.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/gpg.py')
-rw-r--r--cloudinit/gpg.py52
1 files changed, 42 insertions, 10 deletions
diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py
index d58d73e0..7fe17a2e 100644
--- a/cloudinit/gpg.py
+++ b/cloudinit/gpg.py
@@ -10,6 +10,8 @@
from cloudinit import log as logging
from cloudinit import util
+import time
+
LOG = logging.getLogger(__name__)
@@ -25,16 +27,46 @@ def export_armour(key):
return armour
-def recv_key(key, keyserver):
- """Receive gpg key from the specified keyserver"""
- LOG.debug('Receive gpg key "%s"', key)
- try:
- util.subp(["gpg", "--keyserver", keyserver, "--recv", key],
- capture=True)
- except util.ProcessExecutionError as error:
- raise ValueError(('Failed to import key "%s" '
- 'from server "%s" - error %s') %
- (key, keyserver, error))
+def recv_key(key, keyserver, retries=(1, 1)):
+ """Receive gpg key from the specified keyserver.
+
+ Retries are done by default because keyservers can be unreliable.
+ Additionally, there is no way to determine the difference between
+ a non-existant key and a failure. In both cases gpg (at least 2.2.4)
+ exits with status 2 and stderr: "keyserver receive failed: No data"
+ It is assumed that a key provided to cloud-init exists on the keyserver
+ so re-trying makes better sense than failing.
+
+ @param key: a string key fingerprint (as passed to gpg --recv-keys).
+ @param keyserver: the keyserver to request keys from.
+ @param retries: an iterable of sleep lengths for retries.
+ Use None to indicate no retries."""
+ LOG.debug("Importing key '%s' from keyserver '%s'", key, keyserver)
+ cmd = ["gpg", "--keyserver=%s" % keyserver, "--recv-keys", key]
+ if retries is None:
+ retries = []
+ trynum = 0
+ error = None
+ sleeps = iter(retries)
+ while True:
+ trynum += 1
+ try:
+ util.subp(cmd, capture=True)
+ LOG.debug("Imported key '%s' from keyserver '%s' on try %d",
+ key, keyserver, trynum)
+ return
+ except util.ProcessExecutionError as e:
+ error = e
+ try:
+ naplen = next(sleeps)
+ LOG.debug(
+ "Import failed with exit code %d, will try again in %ss",
+ error.exit_code, naplen)
+ time.sleep(naplen)
+ except StopIteration:
+ raise ValueError(
+ ("Failed to import key '%s' from keyserver '%s' "
+ "after %d tries: %s") % (key, keyserver, trynum, error))
def delete_key(key):