Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
ansible
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
ansible
Commits
1721357a
Commit
1721357a
authored
Jan 18, 2013
by
Michael DeHaan
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1824 from jvantuyl/apt-key-module
add apt_key module
parents
fa953ba6
ad637343
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
408 additions
and
0 deletions
+408
-0
library/apt_key
+256
-0
test/TestRunner.py
+62
-0
test/apt_key.gpg
+90
-0
No files found.
library/apt_key
0 → 100644
View file @
1721357a
#!/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
()
test/TestRunner.py
View file @
1721357a
...
@@ -10,6 +10,7 @@ import os
...
@@ -10,6 +10,7 @@ import os
import
shutil
import
shutil
import
time
import
time
import
tempfile
import
tempfile
import
urllib2
from
nose.plugins.skip
import
SkipTest
from
nose.plugins.skip
import
SkipTest
...
@@ -288,3 +289,64 @@ class TestRunner(unittest.TestCase):
...
@@ -288,3 +289,64 @@ class TestRunner(unittest.TestCase):
])
])
print
result
print
result
assert
result
[
'changed'
]
==
False
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
)
test/apt_key.gpg
0 → 100644
View file @
1721357a
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.10 (GNU/Linux)
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
hqsRKcw+Z9k=
=sw5O
-----END PGP PUBLIC KEY BLOCK-----
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment