Charmed-Kubernetes/nrpe/mod/charmhelpers/tests/fetch/test_fetch_ubuntu.py

1112 lines
46 KiB
Python

import six
import subprocess
import io
import os
from tests.helpers import patch_open
from testtools import TestCase
from mock import (
patch,
MagicMock,
call,
sentinel,
)
from charmhelpers.fetch import ubuntu as fetch
if six.PY3:
builtin_open = 'builtins.open'
else:
builtin_open = '__builtin__.open'
# mocked return of openstack.get_distrib_codename()
FAKE_CODENAME = 'precise'
url = 'deb ' + fetch.CLOUD_ARCHIVE_URL
UCA_SOURCES = [
('cloud:precise-folsom/proposed', url + ' precise-proposed/folsom main'),
('cloud:precise-folsom', url + ' precise-updates/folsom main'),
('cloud:precise-folsom/updates', url + ' precise-updates/folsom main'),
('cloud:precise-grizzly/proposed', url + ' precise-proposed/grizzly main'),
('cloud:precise-grizzly', url + ' precise-updates/grizzly main'),
('cloud:precise-grizzly/updates', url + ' precise-updates/grizzly main'),
('cloud:precise-havana/proposed', url + ' precise-proposed/havana main'),
('cloud:precise-havana', url + ' precise-updates/havana main'),
('cloud:precise-havana/updates', url + ' precise-updates/havana main'),
('cloud:precise-icehouse/proposed',
url + ' precise-proposed/icehouse main'),
('cloud:precise-icehouse', url + ' precise-updates/icehouse main'),
('cloud:precise-icehouse/updates', url + ' precise-updates/icehouse main'),
]
PGP_KEY_ASCII_ARMOR = """-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: SKS 1.1.5
Comment: Hostname: keyserver.ubuntu.com
mI0EUCEyTAEEAMuUxyfiegCCwn4J/c0nw5PUTSJdn5FqiUTq6iMfij65xf1vl0g/Mxqw0gfg
AJIsCDvO9N9dloLAwF6FUBMg5My7WyhRPTAKF505TKJboyX3Pp4J1fU1LV8QFVOp87vUh1Rz
B6GU7cSglhnbL85gmbJTllkzkb3h4Yw7W+edjcQ/ABEBAAG0K0xhdW5jaHBhZCBQUEEgZm9y
IFVidW50dSBDbG91ZCBBcmNoaXZlIFRlYW2IuAQTAQIAIgUCUCEyTAIbAwYLCQgHAwIGFQgC
CQoLBBYCAwECHgECF4AACgkQimhEop9oEE7kJAP/eTBgq3Mhbvo0d8elMOuqZx3nmU7gSyPh
ep0zYIRZ5TJWl/7PRtvp0CJA6N6ZywYTQ/4ANHhpibcHZkh8K0AzUvsGXnJRSFoJeqyDbD91
EhoO+4ZfHs2HvRBQEDZILMa2OyuB497E5Mmyua3HDEOrG2cVLllsUZzpTFCx8NgeMHk=
=jLBm
-----END PGP PUBLIC KEY BLOCK-----"""
PGP_KEY_BIN_PGP = b'\x98\x8d\x04P!2L\x01\x04\x00\xcb\x94\xc7\'\xe2z\x00\x82\xc2~\t\xfd\xcd\'\xc3\x93\xd4M"]\x9f\x91j\x89D\xea\xea#\x1f\x8a>\xb9\xc5\xfdo\x97H?3\x1a\xb0\xd2\x07\xe0\x00\x92,\x08;\xce\xf4\xdf]\x96\x82\xc0\xc0^\x85P\x13 \xe4\xcc\xbb[(Q=0\n\x17\x9d9L\xa2[\xa3%\xf7>\x9e\t\xd5\xf55-_\x10\x15S\xa9\xf3\xbb\xd4\x87Ts\x07\xa1\x94\xed\xc4\xa0\x96\x19\xdb/\xce`\x99\xb2S\x96Y3\x91\xbd\xe1\xe1\x8c;[\xe7\x9d\x8d\xc4?\x00\x11\x01\x00\x01\xb4+Launchpad PPA for Ubuntu Cloud Archive Team\x88\xb8\x04\x13\x01\x02\x00"\x05\x02P!2L\x02\x1b\x03\x06\x0b\t\x08\x07\x03\x02\x06\x15\x08\x02\t\n\x0b\x04\x16\x02\x03\x01\x02\x1e\x01\x02\x17\x80\x00\n\t\x10\x8ahD\xa2\x9fh\x10N\xe4$\x03\xffy0`\xabs!n\xfa4w\xc7\xa50\xeb\xaag\x1d\xe7\x99N\xe0K#\xe1z\x9d3`\x84Y\xe52V\x97\xfe\xcfF\xdb\xe9\xd0"@\xe8\xde\x99\xcb\x06\x13C\xfe\x004xi\x89\xb7\x07fH|+@3R\xfb\x06^rQHZ\tz\xac\x83l?u\x12\x1a\x0e\xfb\x86_\x1e\xcd\x87\xbd\x10P\x106H,\xc6\xb6;+\x81\xe3\xde\xc4\xe4\xc9\xb2\xb9\xad\xc7\x0cC\xab\x1bg\x15.YlQ\x9c\xe9LP\xb1\xf0\xd8\x1e0y' # noqa
# a keyid can be retrieved by the ASCII armor-encoded key using this:
# cat testkey.asc | gpg --with-colons --import-options import-show --dry-run
# --import
PGP_KEY_ID = '8a6844a29f68104e'
FAKE_APT_CACHE = {
# an installed package
'vim': {
'current_ver': '2:7.3.547-6ubuntu5'
},
# a uninstalled installation candidate
'emacs': {
}
}
def fake_apt_cache(in_memory=True, progress=None):
def _get(package):
pkg = MagicMock()
if package not in FAKE_APT_CACHE:
raise KeyError
pkg.name = package
if 'current_ver' in FAKE_APT_CACHE[package]:
pkg.current_ver.ver_str = FAKE_APT_CACHE[package]['current_ver']
else:
pkg.current_ver = None
return pkg
cache = MagicMock()
cache.__getitem__.side_effect = _get
return cache
class FetchTest(TestCase):
def setUp(self):
super(FetchTest, self).setUp()
self.patch(fetch, 'get_apt_dpkg_env', lambda: {})
@patch("charmhelpers.fetch.ubuntu.log")
@patch.object(fetch, 'apt_cache')
def test_filter_packages_missing_ubuntu(self, cache, log):
cache.side_effect = fake_apt_cache
result = fetch.filter_installed_packages(['vim', 'emacs'])
self.assertEquals(result, ['emacs'])
@patch("charmhelpers.fetch.ubuntu.log")
@patch.object(fetch, 'apt_cache')
def test_filter_packages_none_missing_ubuntu(self, cache, log):
cache.side_effect = fake_apt_cache
result = fetch.filter_installed_packages(['vim'])
self.assertEquals(result, [])
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'apt_cache')
def test_filter_packages_not_available_ubuntu(self, cache, log):
cache.side_effect = fake_apt_cache
result = fetch.filter_installed_packages(['vim', 'joe'])
self.assertEquals(result, ['joe'])
log.assert_called_with('Package joe has no installation candidate.',
level='WARNING')
@patch('charmhelpers.fetch.ubuntu.filter_installed_packages')
def test_filter_missing_packages(self, filter_installed_packages):
filter_installed_packages.return_value = ['pkga']
self.assertEqual(['pkgb'],
fetch.filter_missing_packages(['pkga', 'pkgb']))
@patch.object(fetch, 'log', lambda *args, **kwargs: None)
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
def test_import_apt_key_radix(self, dearmor_gpg_key,
w_keyfile):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
with patch('subprocess.check_output') as _subp_check_output:
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
_subp_check_output.side_effect = check_output_side_effect
fetch.import_key(PGP_KEY_ID)
_subp_check_output.assert_called_with(curl_cmd, env=None)
w_keyfile.assert_called_once_with(key_name=PGP_KEY_ID,
key_material=PGP_KEY_BIN_PGP)
@patch.object(fetch, 'log', lambda *args, **kwargs: None)
@patch.object(os, 'getenv')
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
def test_import_apt_key_radix_https_proxy(self, dearmor_gpg_key,
w_keyfile, getenv):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
def get_env_side_effect(var):
return {
'HTTPS_PROXY': 'http://squid.internal:3128',
'JUJU_CHARM_HTTPS_PROXY': None,
}[var]
getenv.side_effect = get_env_side_effect
with patch('subprocess.check_output') as _subp_check_output:
proxy_settings = {
'HTTPS_PROXY': 'http://squid.internal:3128',
'https_proxy': 'http://squid.internal:3128',
}
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
_subp_check_output.side_effect = check_output_side_effect
fetch.import_key(PGP_KEY_ID)
_subp_check_output.assert_called_with(curl_cmd, env=proxy_settings)
w_keyfile.assert_called_once_with(key_name=PGP_KEY_ID,
key_material=PGP_KEY_BIN_PGP)
@patch.object(fetch, 'log', lambda *args, **kwargs: None)
@patch.object(os, 'getenv')
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
def test_import_apt_key_radix_charm_https_proxy(self, dearmor_gpg_key,
w_keyfile, getenv):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
def get_env_side_effect(var):
return {
'HTTPS_PROXY': None,
'JUJU_CHARM_HTTPS_PROXY': 'http://squid.internal:3128',
}[var]
getenv.side_effect = get_env_side_effect
with patch('subprocess.check_output') as _subp_check_output:
proxy_settings = {
'HTTPS_PROXY': 'http://squid.internal:3128',
'https_proxy': 'http://squid.internal:3128',
}
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
_subp_check_output.side_effect = check_output_side_effect
fetch.import_key(PGP_KEY_ID)
_subp_check_output.assert_called_with(curl_cmd, env=proxy_settings)
w_keyfile.assert_called_once_with(key_name=PGP_KEY_ID,
key_material=PGP_KEY_BIN_PGP)
@patch.object(fetch, 'log', lambda *args, **kwargs: None)
@patch.object(fetch, '_dearmor_gpg_key')
@patch('subprocess.check_output')
def test_import_bad_apt_key(self, check_output, dearmor_gpg_key):
"""Ensure error when importing apt key fails"""
errmsg = ('Invalid GPG key material. Check your network setup'
' (MTU, routing, DNS) and/or proxy server settings'
' as well as destination keyserver status.')
bad_keyid = 'foo'
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(bad_keyid)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): 'foobar',
}[' '.join(command)]
check_output.side_effect = check_output_side_effect
def dearmor_side_effect(key_asc):
raise fetch.GPGKeyError(errmsg)
dearmor_gpg_key.side_effect = dearmor_side_effect
try:
fetch.import_key(bad_keyid)
assert False
except fetch.GPGKeyError as e:
self.assertEqual(str(e), errmsg)
@patch('charmhelpers.fetch.ubuntu.log')
def test_add_source_none_ubuntu(self, log):
fetch.add_source(source=None)
self.assertTrue(log.called)
@patch('subprocess.check_call')
def test_add_source_ppa(self, check_call):
source = "ppa:test-ppa"
fetch.add_source(source=source)
check_call.assert_called_with(
['add-apt-repository', '--yes', source], env={})
@patch("charmhelpers.fetch.ubuntu.log")
@patch('subprocess.check_call')
@patch('time.sleep')
def test_add_source_ppa_retries_30_times(self, sleep, check_call, log):
self.call_count = 0
def side_effect(*args, **kwargs):
"""Raise an 3 times, then return 0 """
self.call_count += 1
if self.call_count <= fetch.CMD_RETRY_COUNT:
raise subprocess.CalledProcessError(
returncode=1, cmd="some add-apt-repository command")
else:
return 0
check_call.side_effect = side_effect
source = "ppa:test-ppa"
fetch.add_source(source=source)
check_call.assert_called_with(
['add-apt-repository', '--yes', source], env={})
sleep.assert_called_with(10)
self.assertTrue(fetch.CMD_RETRY_COUNT, sleep.call_count)
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_call')
def test_add_source_http_ubuntu(self, check_call, log):
source = "http://archive.ubuntu.com/ubuntu raring-backports main"
fetch.add_source(source=source)
check_call.assert_called_with(
['add-apt-repository', '--yes', source], env={})
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_call')
def test_add_source_https(self, check_call, log):
source = "https://example.com"
fetch.add_source(source=source)
check_call.assert_called_with(
['add-apt-repository', '--yes', source], env={})
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_call')
def test_add_source_deb(self, check_call, log):
"""add-apt-repository behaves differently when using the deb prefix.
$ add-apt-repository --yes \
"http://special.example.com/ubuntu precise-special main"
$ grep special /etc/apt/sources.list
deb http://special.example.com/ubuntu precise precise-special main
deb-src http://special.example.com/ubuntu precise precise-special main
$ add-apt-repository --yes \
"deb http://special.example.com/ubuntu precise-special main"
$ grep special /etc/apt/sources.list
deb http://special.example.com/ubuntu precise precise-special main
deb-src http://special.example.com/ubuntu precise precise-special main
deb http://special.example.com/ubuntu precise-special main
deb-src http://special.example.com/ubuntu precise-special main
"""
source = "deb http://archive.ubuntu.com/ubuntu raring-backports main"
fetch.add_source(source=source)
check_call.assert_called_with(
['add-apt-repository', '--yes', source], env={})
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_output')
@patch('subprocess.check_call')
def test_add_source_http_and_key_id(self, check_call, check_output, log,
dearmor_gpg_key,
w_keyfile):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
check_output.side_effect = check_output_side_effect
source = "http://archive.ubuntu.com/ubuntu raring-backports main"
check_call.return_value = 0 # Successful exit code
fetch.add_source(source=source, key=PGP_KEY_ID)
check_call.assert_any_call(
['add-apt-repository', '--yes', source], env={}),
check_output.assert_has_calls([
call(['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)],
env=None),
])
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_output')
@patch('subprocess.check_call')
def test_add_source_https_and_key_id(self, check_call, check_output, log,
dearmor_gpg_key,
w_keyfile):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
check_output.side_effect = check_output_side_effect
check_call.return_value = 0
source = "https://USER:PASS@private-ppa.launchpad.net/project/awesome"
fetch.add_source(source=source, key=PGP_KEY_ID)
check_call.assert_any_call(
['add-apt-repository', '--yes', source], env={}),
check_output.assert_has_calls([
call(['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)],
env=None),
])
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'get_distrib_codename')
@patch('subprocess.check_call')
@patch('subprocess.Popen')
def test_add_source_http_and_key_gpg1(self, popen, check_call,
get_distrib_codename, log,
dearmor_gpg_key,
w_keyfile):
def check_call_side_effect(*args, **kwargs):
# Make sure the gpg key has already been added before the
# add-apt-repository call, as the update could fail otherwise.
popen.assert_called_with(
['gpg', '--with-colons', '--with-fingerprint'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
return 0
source = "http://archive.ubuntu.com/ubuntu raring-backports main"
key = PGP_KEY_ASCII_ARMOR
key_bytes = PGP_KEY_ASCII_ARMOR.encode('utf-8')
get_distrib_codename.return_value = 'trusty'
check_call.side_effect = check_call_side_effect
expected_key = '35F77D63B5CEC106C577ED856E85A86E4652B4E6'
if six.PY3:
popen.return_value.communicate.return_value = [b"""
pub:-:1024:1:6E85A86E4652B4E6:2009-01-18:::-:Launchpad PPA for Landscape:
fpr:::::::::35F77D63B5CEC106C577ED856E85A86E4652B4E6:
""", b'']
else:
popen.return_value.communicate.return_value = ["""
pub:-:1024:1:6E85A86E4652B4E6:2009-01-18:::-:Launchpad PPA for Landscape:
fpr:::::::::35F77D63B5CEC106C577ED856E85A86E4652B4E6:
""", '']
dearmor_gpg_key.return_value = PGP_KEY_BIN_PGP
fetch.add_source(source=source, key=key)
popen.assert_called_with(
['gpg', '--with-colons', '--with-fingerprint'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
dearmor_gpg_key.assert_called_with(key_bytes)
w_keyfile.assert_called_with(key_name=expected_key,
key_material=PGP_KEY_BIN_PGP)
check_call.assert_any_call(
['add-apt-repository', '--yes', source], env={}),
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'get_distrib_codename')
@patch('subprocess.check_call')
@patch('subprocess.Popen')
def test_add_source_http_and_key_gpg2(self, popen, check_call,
get_distrib_codename, log,
dearmor_gpg_key,
w_keyfile):
def check_call_side_effect(*args, **kwargs):
# Make sure the gpg key has already been added before the
# add-apt-repository call, as the update could fail otherwise.
popen.assert_called_with(
['gpg', '--with-colons', '--with-fingerprint'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
return 0
source = "http://archive.ubuntu.com/ubuntu raring-backports main"
key = PGP_KEY_ASCII_ARMOR
key_bytes = PGP_KEY_ASCII_ARMOR.encode('utf-8')
get_distrib_codename.return_value = 'bionic'
check_call.side_effect = check_call_side_effect
expected_key = '35F77D63B5CEC106C577ED856E85A86E4652B4E6'
if six.PY3:
popen.return_value.communicate.return_value = [b"""
fpr:::::::::35F77D63B5CEC106C577ED856E85A86E4652B4E6:
uid:-::::1232306042::52FE92E6867B4C099AA1A1877A804A965F41A98C::ppa::::::::::0:
""", b'']
else:
# python2 on a distro with gpg2 (unlikely, but possible)
popen.return_value.communicate.return_value = ["""
fpr:::::::::35F77D63B5CEC106C577ED856E85A86E4652B4E6:
uid:-::::1232306042::52FE92E6867B4C099AA1A1877A804A965F41A98C::ppa::::::::::0:
""", '']
dearmor_gpg_key.return_value = PGP_KEY_BIN_PGP
fetch.add_source(source=source, key=key)
popen.assert_called_with(
['gpg', '--with-colons', '--with-fingerprint'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
dearmor_gpg_key.assert_called_with(key_bytes)
w_keyfile.assert_called_with(key_name=expected_key,
key_material=PGP_KEY_BIN_PGP)
check_call.assert_any_call(
['add-apt-repository', '--yes', source], env={}),
def test_add_source_cloud_invalid_pocket(self):
source = "cloud:havana-updates"
self.assertRaises(fetch.SourceConfigError,
fetch.add_source, source)
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'filter_installed_packages')
@patch.object(fetch, 'apt_install')
@patch.object(fetch, 'get_distrib_codename')
def test_add_source_cloud_pocket_style(self, get_distrib_codename,
apt_install,
filter_pkg, log):
source = "cloud:precise-updates/havana"
get_distrib_codename.return_value = 'precise'
result = ('# Ubuntu Cloud Archive\n'
'deb http://ubuntu-cloud.archive.canonical.com/ubuntu'
' precise-updates/havana main\n')
with patch_open() as (mock_open, mock_file):
fetch.add_source(source=source)
mock_file.write.assert_called_with(result)
filter_pkg.assert_called_with(['ubuntu-cloud-keyring'])
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'filter_installed_packages')
@patch.object(fetch, 'apt_install')
@patch.object(fetch, 'get_distrib_codename')
def test_add_source_cloud_os_style(self, get_distrib_codename, apt_install,
filter_pkg, log):
source = "cloud:precise-havana"
get_distrib_codename.return_value = 'precise'
result = ('# Ubuntu Cloud Archive\n'
'deb http://ubuntu-cloud.archive.canonical.com/ubuntu'
' precise-updates/havana main\n')
with patch_open() as (mock_open, mock_file):
fetch.add_source(source=source)
mock_file.write.assert_called_with(result)
filter_pkg.assert_called_with(['ubuntu-cloud-keyring'])
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'filter_installed_packages')
@patch.object(fetch, 'apt_install')
def test_add_source_cloud_distroless_style(self, apt_install,
filter_pkg, log):
source = "cloud:havana"
result = ('# Ubuntu Cloud Archive\n'
'deb http://ubuntu-cloud.archive.canonical.com/ubuntu'
' precise-updates/havana main\n')
with patch_open() as (mock_open, mock_file):
fetch.add_source(source=source)
mock_file.write.assert_called_with(result)
filter_pkg.assert_called_with(['ubuntu-cloud-keyring'])
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'get_distrib_codename')
@patch('platform.machine')
def test_add_source_proposed_x86_64(self, _machine,
get_distrib_codename, log):
source = "proposed"
result = ('# Proposed\n'
'deb http://archive.ubuntu.com/ubuntu precise-proposed'
' main universe multiverse restricted\n')
get_distrib_codename.return_value = 'precise'
_machine.return_value = 'x86_64'
with patch_open() as (mock_open, mock_file):
fetch.add_source(source=source)
mock_file.write.assert_called_with(result)
@patch('charmhelpers.fetch.ubuntu.log')
@patch.object(fetch, 'get_distrib_codename')
@patch('platform.machine')
def test_add_source_proposed_ppc64le(self, _machine,
get_distrib_codename, log):
source = "proposed"
result = (
"# Proposed\n"
"deb http://ports.ubuntu.com/ubuntu-ports precise-proposed main "
"universe multiverse restricted\n")
get_distrib_codename.return_value = 'precise'
_machine.return_value = 'ppc64le'
with patch_open() as (mock_open, mock_file):
fetch.add_source(source=source)
mock_file.write.assert_called_with(result)
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_output')
@patch('subprocess.check_call')
def test_add_source_http_and_key_id_ubuntu(self, check_call, check_output,
log, dearmor_gpg_key,
w_keyfile):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
check_output.side_effect = check_output_side_effect
check_call.return_value = 0
source = "http://archive.ubuntu.com/ubuntu raring-backports main"
key_id = PGP_KEY_ID
fetch.add_source(source=source, key=key_id)
check_call.assert_any_call(
['add-apt-repository', '--yes', source], env={}),
check_output.assert_has_calls([
call(['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)],
env=None),
])
@patch.object(fetch, '_write_apt_gpg_keyfile')
@patch.object(fetch, '_dearmor_gpg_key')
@patch('charmhelpers.fetch.ubuntu.log')
@patch('subprocess.check_output')
@patch('subprocess.check_call')
def test_add_source_https_and_key_id_ubuntu(self, check_call, check_output,
log, dearmor_gpg_key,
w_keyfile):
def dearmor_side_effect(key_asc):
return {
PGP_KEY_ASCII_ARMOR: PGP_KEY_BIN_PGP,
}[key_asc]
dearmor_gpg_key.side_effect = dearmor_side_effect
curl_cmd = ['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)]
def check_output_side_effect(command, env):
return {
' '.join(curl_cmd): PGP_KEY_ASCII_ARMOR,
}[' '.join(command)]
check_output.side_effect = check_output_side_effect
check_call.return_value = 0
source = "https://USER:PASS@private-ppa.launchpad.net/project/awesome"
fetch.add_source(source=source, key=PGP_KEY_ID)
check_call.assert_any_call(
['add-apt-repository', '--yes', source], env={}),
check_output.assert_has_calls([
call(['curl', ('https://keyserver.ubuntu.com'
'/pks/lookup?op=get&options=mr'
'&exact=on&search=0x{}').format(PGP_KEY_ID)],
env=None),
])
@patch('charmhelpers.fetch.ubuntu.log')
def test_configure_bad_install_source(self, log):
try:
fetch.add_source('foo', fail_invalid=True)
self.fail("Calling add_source('foo') should fail")
except fetch.SourceConfigError as e:
self.assertEqual(str(e), "Unknown source: 'foo'")
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_configure_install_source_uca_staging(self, _lsb):
"""Test configuring installation source from UCA staging sources"""
_lsb.return_value = FAKE_CODENAME
# staging pockets are configured as PPAs
with patch('subprocess.check_call') as _subp:
src = 'cloud:precise-folsom/staging'
fetch.add_source(src)
cmd = ['add-apt-repository', '-y',
'ppa:ubuntu-cloud-archive/folsom-staging']
_subp.assert_called_with(cmd, env={})
@patch(builtin_open)
@patch('charmhelpers.fetch.ubuntu.apt_install')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
@patch('charmhelpers.fetch.ubuntu.filter_installed_packages')
def test_configure_install_source_uca_repos(
self, _fip, _lsb, _install, _open):
"""Test configuring installation source from UCA sources"""
_lsb.return_value = FAKE_CODENAME
_file = MagicMock(spec=io.FileIO)
_open.return_value = _file
_fip.side_effect = lambda x: x
for src, url in UCA_SOURCES:
actual_url = "# Ubuntu Cloud Archive\n{}\n".format(url)
fetch.add_source(src)
_install.assert_called_with(['ubuntu-cloud-keyring'],
fatal=True)
_open.assert_called_with(
'/etc/apt/sources.list.d/cloud-archive.list',
'w'
)
_file.__enter__().write.assert_called_with(actual_url)
def test_configure_install_source_bad_uca(self):
"""Test configuring installation source from bad UCA source"""
try:
fetch.add_source('cloud:foo-bar', fail_invalid=True)
self.fail("add_source('cloud:foo-bar') should fail")
except fetch.SourceConfigError as e:
_e = ('Invalid Cloud Archive release specified: foo-bar'
' on this Ubuntuversion')
self.assertTrue(str(e).startswith(_e))
@patch('charmhelpers.fetch.ubuntu.log')
def test_add_unparsable_source(self, log_):
source = "propsed" # Minor typo
fetch.add_source(source=source)
self.assertEqual(1, log_.call_count)
@patch('charmhelpers.fetch.ubuntu.log')
def test_add_distro_source(self, log):
source = "distro"
# distro is a noop but test validate no exception is thrown
fetch.add_source(source=source)
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_is_distro(
self, mock_get_distrib_codename, mock_add_cloud_pocket):
mock_get_distrib_codename.return_value = 'focal'
fetch.add_source('ussuri')
mock_add_cloud_pocket.assert_not_called()
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_is_cloud_pocket(
self, mock_get_distrib_codename, mock_add_cloud_pocket):
mock_get_distrib_codename.return_value = 'bionic'
fetch.add_source('ussuri')
mock_add_cloud_pocket.assert_called_once_with("bionic-ussuri")
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_impossible_version(
self, mock_get_distrib_codename, mock_add_cloud_pocket):
mock_get_distrib_codename.return_value = 'xenial'
try:
fetch.add_source('ussuri')
self.fail("add_source('ussuri') on xenial should fail")
except fetch.SourceConfigError:
pass
mock_add_cloud_pocket.assert_not_called()
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_impossible_ubuntu(
self, mock_get_distrib_codename, mock_add_cloud_pocket):
mock_get_distrib_codename.return_value = 'bambam'
try:
fetch.add_source('ussuri')
self.fail("add_source('ussuri') on bambam should fail")
except fetch.SourceConfigError:
pass
mock_add_cloud_pocket.assert_not_called()
@patch('charmhelpers.fetch.ubuntu._add_proposed')
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_proposed_is_distro_proposed(
self, mock_get_distrib_codename, mock_add_cloud_pocket,
mock_add_proposed):
mock_get_distrib_codename.return_value = 'focal'
fetch.add_source('ussuri/proposed')
mock_add_cloud_pocket.assert_not_called()
mock_add_proposed.assert_called_once_with()
@patch('charmhelpers.fetch.ubuntu._add_proposed')
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_proposed_is_cloud_pocket(
self, mock_get_distrib_codename, mock_add_cloud_pocket,
mock_add_proposed):
mock_get_distrib_codename.return_value = 'bionic'
fetch.add_source('ussuri/proposed')
mock_add_cloud_pocket.assert_called_once_with("bionic-ussuri/proposed")
mock_add_proposed.assert_not_called()
@patch('charmhelpers.fetch.ubuntu._add_proposed')
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_proposed_impossible_version(
self, mock_get_distrib_codename, mock_add_cloud_pocket,
mock_add_proposed):
mock_get_distrib_codename.return_value = 'xenial'
try:
fetch.add_source('ussuri/proposed')
self.fail("add_source('ussuri/proposed') on xenial should fail")
except fetch.SourceConfigError:
pass
mock_add_cloud_pocket.assert_not_called()
mock_add_proposed.assert_not_called()
@patch('charmhelpers.fetch.ubuntu._add_proposed')
@patch('charmhelpers.fetch.ubuntu._add_cloud_pocket')
@patch('charmhelpers.fetch.ubuntu.get_distrib_codename')
def test_add_bare_openstack_proposed_impossible_ubuntu(
self, mock_get_distrib_codename, mock_add_cloud_pocket,
mock_add_proposed):
mock_get_distrib_codename.return_value = 'bambam'
try:
fetch.add_source('ussuri/proposed')
self.fail("add_source('ussuri/proposed') on bambam should fail")
except fetch.SourceConfigError:
pass
mock_add_cloud_pocket.assert_not_called()
mock_add_proposed.assert_not_called()
class AptTests(TestCase):
def setUp(self):
super(AptTests, self).setUp()
self.patch(fetch, 'get_apt_dpkg_env', lambda: {})
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_apt_upgrade_non_fatal(self, log, mock_call):
options = ['--foo', '--bar']
fetch.apt_upgrade(options)
mock_call.assert_called_with(
['apt-get', '--assume-yes',
'--foo', '--bar', 'upgrade'],
env={})
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_apt_upgrade_fatal(self, log, mock_call):
options = ['--foo', '--bar']
fetch.apt_upgrade(options, fatal=True)
mock_call.assert_called_with(
['apt-get', '--assume-yes',
'--foo', '--bar', 'upgrade'],
env={})
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_apt_dist_upgrade_fatal(self, log, mock_call):
options = ['--foo', '--bar']
fetch.apt_upgrade(options, fatal=True, dist=True)
mock_call.assert_called_with(
['apt-get', '--assume-yes',
'--foo', '--bar', 'dist-upgrade'],
env={})
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_installs_apt_packages(self, log, mock_call):
packages = ['foo', 'bar']
options = ['--foo', '--bar']
fetch.apt_install(packages, options)
mock_call.assert_called_with(
['apt-get', '--assume-yes',
'--foo', '--bar', 'install', 'foo', 'bar'],
env={})
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_installs_apt_packages_without_options(self, log, mock_call):
packages = ['foo', 'bar']
fetch.apt_install(packages)
mock_call.assert_called_with(
['apt-get', '--assume-yes',
'--option=Dpkg::Options::=--force-confold',
'install', 'foo', 'bar'],
env={})
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_installs_apt_packages_as_string(self, log, mock_call):
packages = 'foo bar'
options = ['--foo', '--bar']
fetch.apt_install(packages, options)
mock_call.assert_called_with(
['apt-get', '--assume-yes',
'--foo', '--bar', 'install', 'foo bar'],
env={})
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_installs_apt_packages_with_possible_errors(self, log,
check_call):
packages = ['foo', 'bar']
options = ['--foo', '--bar']
fetch.apt_install(packages, options, fatal=True)
check_call.assert_called_with(
['apt-get', '--assume-yes',
'--foo', '--bar', 'install', 'foo', 'bar'],
env={})
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_purges_apt_packages_as_string_fatal(self, log, mock_call):
packages = 'irrelevant names'
mock_call.side_effect = OSError('fail')
self.assertRaises(OSError, fetch.apt_purge, packages, fatal=True)
self.assertTrue(log.called)
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_purges_apt_packages_fatal(self, log, mock_call):
packages = ['irrelevant', 'names']
mock_call.side_effect = OSError('fail')
self.assertRaises(OSError, fetch.apt_purge, packages, fatal=True)
self.assertTrue(log.called)
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_purges_apt_packages_as_string_nofatal(self, log, mock_call):
packages = 'foo bar'
fetch.apt_purge(packages)
self.assertTrue(log.called)
mock_call.assert_called_with(
['apt-get', '--assume-yes', 'purge', 'foo bar'],
env={})
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_purges_apt_packages_nofatal(self, log, mock_call):
packages = ['foo', 'bar']
fetch.apt_purge(packages)
self.assertTrue(log.called)
mock_call.assert_called_with(
['apt-get', '--assume-yes', 'purge', 'foo', 'bar'],
env={})
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_mark_apt_packages_as_string_fatal(self, log, mock_call):
packages = 'irrelevant names'
mock_call.side_effect = OSError('fail')
self.assertRaises(OSError, fetch.apt_mark, packages, sentinel.mark,
fatal=True)
self.assertTrue(log.called)
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_mark_apt_packages_fatal(self, log, mock_call):
packages = ['irrelevant', 'names']
mock_call.side_effect = OSError('fail')
self.assertRaises(OSError, fetch.apt_mark, packages, sentinel.mark,
fatal=True)
self.assertTrue(log.called)
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_mark_apt_packages_as_string_nofatal(self, log, mock_call):
packages = 'foo bar'
fetch.apt_mark(packages, sentinel.mark)
self.assertTrue(log.called)
mock_call.assert_called_with(
['apt-mark', sentinel.mark, 'foo bar'],
universal_newlines=True)
@patch('subprocess.call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_mark_apt_packages_nofatal(self, log, mock_call):
packages = ['foo', 'bar']
fetch.apt_mark(packages, sentinel.mark)
self.assertTrue(log.called)
mock_call.assert_called_with(
['apt-mark', sentinel.mark, 'foo', 'bar'],
universal_newlines=True)
@patch('subprocess.check_call')
@patch('charmhelpers.fetch.ubuntu.log')
def test_mark_apt_packages_nofatal_abortonfatal(self, log, mock_call):
packages = ['foo', 'bar']
fetch.apt_mark(packages, sentinel.mark, fatal=True)
self.assertTrue(log.called)
mock_call.assert_called_with(
['apt-mark', sentinel.mark, 'foo', 'bar'],
universal_newlines=True)
@patch('charmhelpers.fetch.ubuntu.apt_mark')
def test_apt_hold(self, apt_mark):
fetch.apt_hold(sentinel.packages)
apt_mark.assert_called_once_with(sentinel.packages, 'hold',
fatal=False)
@patch('charmhelpers.fetch.ubuntu.apt_mark')
def test_apt_hold_fatal(self, apt_mark):
fetch.apt_hold(sentinel.packages, fatal=sentinel.fatal)
apt_mark.assert_called_once_with(sentinel.packages, 'hold',
fatal=sentinel.fatal)
@patch('charmhelpers.fetch.ubuntu.apt_mark')
def test_apt_unhold(self, apt_mark):
fetch.apt_unhold(sentinel.packages)
apt_mark.assert_called_once_with(sentinel.packages, 'unhold',
fatal=False)
@patch('charmhelpers.fetch.ubuntu.apt_mark')
def test_apt_unhold_fatal(self, apt_mark):
fetch.apt_unhold(sentinel.packages, fatal=sentinel.fatal)
apt_mark.assert_called_once_with(sentinel.packages, 'unhold',
fatal=sentinel.fatal)
@patch('subprocess.check_call')
def test_apt_update_fatal(self, check_call):
fetch.apt_update(fatal=True)
check_call.assert_called_with(
['apt-get', 'update'],
env={})
@patch('subprocess.call')
def test_apt_update_nonfatal(self, call):
fetch.apt_update()
call.assert_called_with(
['apt-get', 'update'],
env={})
@patch('subprocess.check_call')
@patch('time.sleep')
def test_run_apt_command_retries_if_fatal(self, check_call, sleep):
"""The _run_apt_command function retries the command if it can't get
the APT lock."""
self.called = False
def side_effect(*args, **kwargs):
"""
First, raise an exception (can't acquire lock), then return 0
(the lock is grabbed).
"""
if not self.called:
self.called = True
raise subprocess.CalledProcessError(
returncode=100, cmd="some command")
else:
return 0
check_call.side_effect = side_effect
check_call.return_value = 0
from charmhelpers.fetch.ubuntu import _run_apt_command
_run_apt_command(["some", "command"], fatal=True)
self.assertTrue(sleep.called)
@patch.object(fetch, 'apt_cache')
def test_get_upstream_version(self, cache):
cache.side_effect = fake_apt_cache
self.assertEqual(fetch.get_upstream_version('vim'), '7.3.547')
self.assertEqual(fetch.get_upstream_version('emacs'), None)
self.assertEqual(fetch.get_upstream_version('unknown'), None)
@patch('charmhelpers.fetch.ubuntu._run_apt_command')
def test_apt_autoremove_fatal(self, run_apt_command):
fetch.apt_autoremove(purge=True, fatal=True)
run_apt_command.assert_called_with(
['apt-get', '--assume-yes', 'autoremove', '--purge'],
True
)
@patch('charmhelpers.fetch.ubuntu._run_apt_command')
def test_apt_autoremove_nonfatal(self, run_apt_command):
fetch.apt_autoremove(purge=False, fatal=False)
run_apt_command.assert_called_with(
['apt-get', '--assume-yes', 'autoremove'],
False
)
class TestAptDpkgEnv(TestCase):
@patch.object(fetch, 'get_system_env')
def test_get_apt_dpkg_env(self, mock_get_system_env):
mock_get_system_env.return_value = '/a/path'
self.assertEquals(
fetch.get_apt_dpkg_env(),
{'DEBIAN_FRONTEND': 'noninteractive', 'PATH': '/a/path'})