mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
add apt_key module
Pretty straightforward. Give it a URL with an exported GPG key for signing an Apt repository. It downloads it and will install it using apt-key. It's even smart enough to tell if it's already there (i.e. actually tells you if it changed or not).
This commit is contained in:
parent
f86974fdae
commit
ad6373430b
3 changed files with 408 additions and 0 deletions
256
library/apt_key
Normal file
256
library/apt_key
Normal file
|
@ -0,0 +1,256 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2012, Jayson Vantuyl <jayson@aggressive.ly>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: apt_key
|
||||
author: Jayson Vantuyl
|
||||
version_added: 1.0
|
||||
short_description: Add or remove an apt key
|
||||
description:
|
||||
- Add or remove an I(apt) key, optionally downloading it
|
||||
notes:
|
||||
- doesn't download the key unless it really needs it
|
||||
- as a sanity check, downloaded key id must match the one specified
|
||||
- best practice is to specify the key id and the url
|
||||
options:
|
||||
id:
|
||||
required: false
|
||||
default: none
|
||||
description:
|
||||
- identifier of key
|
||||
url:
|
||||
required: false
|
||||
default: none
|
||||
description:
|
||||
- url to retrieve key from.
|
||||
state:
|
||||
required: false
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
description:
|
||||
- used to specify if key is being added or revoked
|
||||
examples:
|
||||
- code: "apt_key: url=https://ftp-master.debian.org/keys/archive-key-6.0.asc state=present"
|
||||
description: Add an Apt signing key, uses whichever key is at the URL
|
||||
- code: "apt_key: id=473041FA url=https://ftp-master.debian.org/keys/archive-key-6.0.asc state=present"
|
||||
description: Add an Apt signing key, will not download if present
|
||||
- code: "apt_key: url=https://ftp-master.debian.org/keys/archive-key-6.0.asc state=absent"
|
||||
description: Remove an Apt signing key, uses whichever key is at the URL
|
||||
- code: "apt_key: id=473041FA state=absent"
|
||||
description: Remove a Apt specific signing key
|
||||
'''
|
||||
|
||||
from urllib2 import urlopen, URLError
|
||||
from traceback import format_exc
|
||||
from subprocess import Popen, PIPE, call
|
||||
from re import compile as re_compile
|
||||
from distutils.spawn import find_executable
|
||||
from os import environ
|
||||
from sys import exc_info
|
||||
|
||||
match_key = re_compile("^gpg:.*key ([0-9a-fA-F]+):.*$")
|
||||
|
||||
REQUIRED_EXECUTABLES=['gpg', 'grep', 'apt-key']
|
||||
|
||||
|
||||
def find_missing_binaries():
|
||||
return [missing for missing in REQUIRED_EXECUTABLES if not find_executable(missing)]
|
||||
|
||||
|
||||
def get_key_ids(key_data):
|
||||
p = Popen("gpg --list-only --import -", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
(stdo, stde) = p.communicate(key_data)
|
||||
|
||||
if p.returncode > 0:
|
||||
raise Exception("error running GPG to retrieve keys")
|
||||
|
||||
output = stdo + stde
|
||||
|
||||
for line in output.split('\n'):
|
||||
match = match_key.match(line)
|
||||
if match:
|
||||
yield match.group(1)
|
||||
|
||||
|
||||
def key_present(key_id):
|
||||
return call("apt-key list | 2>&1 grep -q %s" % key_id, shell=True) == 0
|
||||
|
||||
|
||||
def download_key(url):
|
||||
if url is None:
|
||||
raise Exception("Needed URL but none specified")
|
||||
connection = urlopen(url)
|
||||
if connection is None:
|
||||
raise Exception("error connecting to download key from %r" % url)
|
||||
return connection.read()
|
||||
|
||||
|
||||
def add_key(key):
|
||||
return call("apt-key add -", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
(_, _) = p.communicate(key)
|
||||
|
||||
return p.returncode == 0
|
||||
|
||||
|
||||
def remove_key(key_id):
|
||||
return call('apt-key del %s' % key_id, shell=True) == 0
|
||||
|
||||
|
||||
def return_values(tb=False):
|
||||
if tb:
|
||||
return {'exception': format_exc()}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
# use cues from the environment to mock out functions for testing
|
||||
if 'ANSIBLE_TEST_APT_KEY' in environ:
|
||||
orig_download_key = download_key
|
||||
KEY_ADDED=0
|
||||
KEY_REMOVED=0
|
||||
KEY_DOWNLOADED=0
|
||||
|
||||
|
||||
def download_key(url):
|
||||
global KEY_DOWNLOADED
|
||||
KEY_DOWNLOADED += 1
|
||||
return orig_download_key(url)
|
||||
|
||||
|
||||
def find_missing_binaries():
|
||||
return []
|
||||
|
||||
|
||||
def add_key(key):
|
||||
global KEY_ADDED
|
||||
KEY_ADDED += 1
|
||||
return True
|
||||
|
||||
|
||||
def remove_key(key_id):
|
||||
global KEY_REMOVED
|
||||
KEY_REMOVED += 1
|
||||
return True
|
||||
|
||||
|
||||
def return_values(tb=False):
|
||||
extra = dict(
|
||||
added=KEY_ADDED,
|
||||
removed=KEY_REMOVED,
|
||||
downloaded=KEY_DOWNLOADED
|
||||
)
|
||||
if tb:
|
||||
extra['exception'] = format_exc()
|
||||
return extra
|
||||
|
||||
|
||||
if environ.get('ANSIBLE_TEST_APT_KEY') == 'none':
|
||||
def key_present(key_id):
|
||||
return False
|
||||
else:
|
||||
def key_present(key_id):
|
||||
return key_id == environ['ANSIBLE_TEST_APT_KEY']
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
id=dict(required=False, default=None),
|
||||
url=dict(required=False),
|
||||
state=dict(required=False, choices=['present', 'absent'], default='present')
|
||||
)
|
||||
)
|
||||
|
||||
expected_key_id = module.params['id']
|
||||
url = module.params['url']
|
||||
state = module.params['state']
|
||||
changed = False
|
||||
|
||||
missing = find_missing_binaries()
|
||||
|
||||
if missing:
|
||||
module.fail_json(msg="can't find needed binaries to run", missing=missing,
|
||||
**return_values())
|
||||
|
||||
if state == 'present':
|
||||
if expected_key_id and key_present(expected_key_id):
|
||||
# key is present, nothing to do
|
||||
pass
|
||||
else:
|
||||
# download key
|
||||
try:
|
||||
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)
|
||||
)
|
||||
|
||||
# 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
|
||||
elif add_key(key):
|
||||
changed=True
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="failed to add key id %s" % key_id,
|
||||
**return_values()
|
||||
)
|
||||
elif state == 'absent':
|
||||
# optionally download the key and get the id
|
||||
if not expected_key_id:
|
||||
try:
|
||||
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):
|
||||
changed=True
|
||||
else:
|
||||
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())
|
||||
|
||||
# include magic from lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
|
@ -10,6 +10,7 @@ import os
|
|||
import shutil
|
||||
import time
|
||||
import tempfile
|
||||
import urllib2
|
||||
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
|
@ -288,3 +289,64 @@ class TestRunner(unittest.TestCase):
|
|||
])
|
||||
print result
|
||||
assert result['changed'] == False
|
||||
|
||||
def test_apt_key(self):
|
||||
try:
|
||||
key_file = self._get_test_file("apt_key.gpg")
|
||||
key_file_url = 'file://' + urllib2.quote(key_file)
|
||||
key_id = '473041FA'
|
||||
|
||||
os.environ['ANSIBLE_TEST_APT_KEY'] = 'none'
|
||||
# key missing, should download and add
|
||||
result = self._run('apt_key', ['state=present', 'url=' + key_file_url])
|
||||
assert 'failed' not in result
|
||||
assert result['added'] == 1
|
||||
assert result['downloaded'] == 1
|
||||
assert result['removed'] == 0
|
||||
assert result['changed']
|
||||
|
||||
os.environ["ANSIBLE_TEST_APT_KEY"] = key_id
|
||||
# key missing, shouldn't download, no changes
|
||||
result = self._run('apt_key', ['id=12345678', 'state=absent', 'url=' + key_file_url])
|
||||
assert 'failed' not in result
|
||||
assert result['added'] == 0
|
||||
assert result['downloaded'] == 0
|
||||
assert result['removed'] == 0
|
||||
assert not result['changed']
|
||||
# key missing, should download and fail sanity check, no changes
|
||||
result = self._run('apt_key', ['id=12345678', 'state=present', 'url=' + key_file_url])
|
||||
assert 'failed' in result
|
||||
assert result['added'] == 0
|
||||
assert result['downloaded'] == 1
|
||||
assert result['removed'] == 0
|
||||
# key present, shouldn't download, no changes
|
||||
result = self._run('apt_key', ['id=' + key_id, 'state=present', 'url=' + key_file_url])
|
||||
assert 'failed' not in result
|
||||
assert result['added'] == 0
|
||||
assert result['downloaded'] == 0
|
||||
assert result['removed'] == 0
|
||||
assert not result['changed']
|
||||
# key present, should download to get key id
|
||||
result = self._run('apt_key', ['state=present', 'url=' + key_file_url])
|
||||
assert 'failed' not in result
|
||||
assert result['added'] == 0
|
||||
assert result['downloaded'] == 1
|
||||
assert result['removed'] == 0
|
||||
assert not result['changed']
|
||||
# key present, should download to get key id and remove
|
||||
result = self._run('apt_key', ['state=absent', 'url=' + key_file_url])
|
||||
assert 'failed' not in result
|
||||
assert result['added'] == 0
|
||||
assert result['downloaded'] == 1
|
||||
assert result['removed'] == 1
|
||||
assert result['changed']
|
||||
# key present, should remove but not download
|
||||
result = self._run('apt_key', ['id=' + key_id, 'state=absent', 'url=' + key_file_url])
|
||||
assert 'failed' not in result
|
||||
assert result['added'] == 0
|
||||
assert result['downloaded'] == 0
|
||||
assert result['removed'] == 1
|
||||
assert result['changed']
|
||||
finally:
|
||||
# always clean up the environment
|
||||
os.environ.pop('ANSIBLE_TEST_APT_KEY', None)
|
||||
|
|
90
test/apt_key.gpg
Normal file
90
test/apt_key.gpg
Normal file
|
@ -0,0 +1,90 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.10 (GNU/Linux)
|
||||
|
||||
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
|
||||
hqsRKcw+Z9k=
|
||||
=sw5O
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
Loading…
Reference in a new issue