This standardizes the apt_key module some

* improves error handling and reporting
* uses run_command to reduce code
* fails quicker on errors as opposed to return codes and tracebacks
* can now also specify the key as data versus needing to wget it from a file
reviewable/pr18780/r1
Michael DeHaan 12 years ago
parent a3bde39e63
commit f83db16c37

@ -22,7 +22,7 @@
DOCUMENTATION = ''' DOCUMENTATION = '''
--- ---
module: apt_key module: apt_key
author: Jayson Vantuyl author: Jayson Vantuyl & others
version_added: 1.0 version_added: 1.0
short_description: Add or remove an apt key short_description: Add or remove an apt key
description: description:
@ -59,145 +59,116 @@ examples:
description: Remove a Apt specific signing key description: Remove a Apt specific signing key
''' '''
# FIXME: standardize into module_common
from urllib2 import urlopen, URLError from urllib2 import urlopen, URLError
from traceback import format_exc from traceback import format_exc
from subprocess import Popen, PIPE, call
from re import compile as re_compile from re import compile as re_compile
# FIXME: standardize into module_common
from distutils.spawn import find_executable from distutils.spawn import find_executable
from os import environ from os import environ
from sys import exc_info from sys import exc_info
import traceback
match_key = re_compile("^gpg:.*key ([0-9a-fA-F]+):.*$") match_key = re_compile("^gpg:.*key ([0-9a-fA-F]+):.*$")
REQUIRED_EXECUTABLES=['gpg', 'grep', 'apt-key'] REQUIRED_EXECUTABLES=['gpg', 'grep', 'apt-key']
def find_missing_binaries(): def check_missing_binaries(module):
return [missing for missing in REQUIRED_EXECUTABLES if not find_executable(missing)] missing = [e for e in REQUIRED_EXECUTABLES if not find_executable(e)]
if len(missing):
module.fail_json(msg="binaries are missing", names=all)
def get_key_ids(key_data):
p = Popen("gpg --list-only --import -", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) def all_keys(module):
(stdo, stde) = p.communicate(key_data) (rc, out, err) = module.run_command("apt-key list")
results = []
if p.returncode > 0: lines = out.split('\n')
raise Exception("error running GPG to retrieve keys") for line in lines:
if line.startswith("pub"):
output = stdo + stde tokens = line.split()
code = tokens[1]
for line in output.split('\n'): (len_type, real_code) = code.split("/")
match = match_key.match(line) results.append(real_code)
if match: return results
yield match.group(1)
def key_present(module, key_id):
(rc, out, err) = module.run_command("apt-key list | 2>&1 grep -q %s" % key_id)
def key_present(key_id): return rc == 0
return call("apt-key list | 2>&1 grep -q %s" % key_id, shell=True) == 0
def download_key(module, url):
# FIXME: move get_url code to common, allow for in-memory D/L, support proxies
def download_key(url): # and reuse here
if url is None: if url is None:
raise Exception("Needed URL but none specified") module.fail_json(msg="needed a URL but was not specified")
try:
connection = urlopen(url) connection = urlopen(url)
if connection is None: if connection is None:
raise Exception("error connecting to download key from %r" % url) module.fail_json("error connecting to download key from url")
return connection.read() data = connection.read()
return data
except:
def add_key(key): module.fail_json(msg="error getting key id from url", traceback=format_exc())
p = Popen("apt-key add -", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
(_, _) = p.communicate(key)
return p.returncode == 0
def add_key(module, key):
cmd = "apt-key add -"
(rc, out, err) = module.run_command(cmd, data=key, check_rc=True)
return True
def remove_key(key_id): def remove_key(key_id):
return call('apt-key del %s' % key_id, shell=True) == 0 # FIXME: use module.run_command, fail at point of error and don't discard useful stdin/stdout
cmd = 'apt-key del %s'
(rc, out, err) = module.run_command(cmd, check_rc=True)
def return_values(tb=False): return True
if tb:
return {'exception': format_exc()}
else:
return {}
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
id=dict(required=False, default=None), id=dict(required=False, default=None),
url=dict(required=False), url=dict(required=False),
data=dict(required=False),
key=dict(required=False),
state=dict(required=False, choices=['present', 'absent'], default='present') state=dict(required=False, choices=['present', 'absent'], default='present')
) ),
) )
expected_key_id = module.params['id'] key_id = module.params['id']
url = module.params['url'] url = module.params['url']
data = module.params['data']
state = module.params['state'] state = module.params['state']
changed = False changed = False
missing = find_missing_binaries() # FIXME: I think we have a common facility for this, if not, want
check_missing_binaries(module)
if missing: keys = all_keys(module)
module.fail_json(msg="can't find needed binaries to run", missing=missing,
**return_values())
if state == 'present': if state == 'present':
if expected_key_id and key_present(expected_key_id): if key_id and key_id in keys:
# key is present, nothing to do module.exit_json(changed=False)
pass
else: else:
# download key if not data:
try: data = download_key(module, url)
key = download_key(url) if key_id and key_id in keys:
(key_id,) = tuple(get_key_ids(key)) # TODO: support multiple key ids? module.exit_json(changed=False)
except Exception: else:
module.fail_json( add_key(module, data)
msg="error getting key id from url",
**return_values(True)
)
# sanity check downloaded key
if expected_key_id and key_id != expected_key_id:
module.fail_json(
msg="expected key id %s, got key id %s" % (expected_key_id, key_id),
**return_values()
)
# actually add key
if key_present(key_id):
changed=False changed=False
elif add_key(key): keys2 = all_keys(module)
if len(keys) != len(keys2):
changed=True changed=True
else: if key_id and not key_id in keys2:
module.fail_json( module.fail_json(msg="key does not seem to have been added", id=key_id)
msg="failed to add key id %s" % key_id, module.exit_json(changed=changed)
**return_values()
)
elif state == 'absent': elif state == 'absent':
# optionally download the key and get the id if not key_id:
if not expected_key_id: module.fail_json(msg="key is required")
try: if key_id in keys:
key = download_key(url)
(key_id,) = tuple(get_key_ids(key)) # TODO: support multiple key ids?
except Exception:
module.fail_json(
msg="error getting key id from url",
**return_values(True)
)
else:
key_id = expected_key_id
# actually remove key
if key_present(key_id):
if remove_key(key_id): if remove_key(key_id):
changed=True changed=True
else: else:
# FIXME: module.fail_json or exit-json immediately at point of failure
module.fail_json(msg="error removing key_id", **return_values(True)) module.fail_json(msg="error removing key_id", **return_values(True))
else:
module.fail_json(
msg="unexpected state: %s" % state,
**return_values()
)
module.exit_json(changed=changed, **return_values()) module.exit_json(changed=changed, **return_values())

Loading…
Cancel
Save