"""Series of integration tests covering apt functionality.""" import re import pytest from cloudinit.config import cc_apt_configure from cloudinit import gpg from tests.integration_tests.clouds import ImageSpecification from tests.integration_tests.instances import IntegrationInstance USER_DATA = """\ #cloud-config apt: conf: | APT { Get { Assume-Yes "true"; Fix-Broken "true"; } } proxy: "http://proxy.internal:3128" http_proxy: "http://squid.internal:3128" ftp_proxy: "ftp://squid.internal:3128" https_proxy: "https://squid.internal:3128" primary: - arches: [default] uri: http://badarchive.ubuntu.com/ubuntu security: - arches: [default] uri: http://badsecurity.ubuntu.com/ubuntu sources_list: | deb $MIRROR $RELEASE main restricted deb-src $MIRROR $RELEASE main restricted deb $PRIMARY $RELEASE universe restricted deb-src $PRIMARY $RELEASE universe restricted deb $SECURITY $RELEASE-security multiverse deb-src $SECURITY $RELEASE-security multiverse sources: test_keyserver: keyid: 72600DB15B8E4C8B1964B868038ACC97C660A937 keyserver: keyserver.ubuntu.com source: "deb http://ppa.launchpad.net/cloud-init-raharper/curtin-dev/ubuntu $RELEASE main" test_ppa: keyid: 441614D8 keyserver: keyserver.ubuntu.com source: "ppa:simplestreams-dev/trunk" test_signed_by: keyid: A2EB2DEC0BD7519B7B38BE38376A290EC8068B11 keyserver: keyserver.ubuntu.com source: "deb [signed-by=$KEY_FILE] http://ppa.launchpad.net/juju/stable/ubuntu $RELEASE main" test_bad_key: key: "" source: "deb $MIRROR $RELEASE main" test_key: source: "deb http://ppa.launchpad.net/cloud-init-dev/test-archive/ubuntu $RELEASE main" key: | -----BEGIN PGP PUBLIC KEY BLOCK----- Version: SKS 1.1.6 Comment: Hostname: keyserver.ubuntu.com mQINBFbZRUIBEAC+A0PIKYBP9kLC4hQtRrffRS11uLo8/BdtmOdrlW0hpPHzCfKnjR3tvSEI lqPHG1QrrjAXKZDnZMRz+h/px7lUztvytGzHPSJd5ARUzAyjyRezUhoJ3VSCxrPqx62avuWf RfoJaIeHfDehL5/dTVkyiWxfVZ369ZX6JN2AgLsQTeybTQ75+2z0xPrrhnGmgh6g0qTYcAaq M5ONOGiqeSBX/Smjh6ALy5XkhUiFGLsI7Yluf6XSICY/x7gd6RAfgSIQrUTNMoS1sqhT4aot +xvOfQy8ySkfAK4NddXql6E/+ZqTmBY/Lr0YklFBy8jGT+UysfiIznPMIwbmgq5Li7BtDDtX b8Uyi4edPpjtextezfXYn4NVIpPL5dPZS/FXh4HpzyH0pYCfrH4QDGA7i52AGmhpiOFjJMo6 N33sdjZHOH/2Vyp+QZaQnsdUAi1N4M6c33tQbpIScn1SY+El8z5JDA4PBzkw8HpLCi1gGoa6 V4kfbWqXXbGAJFkLkP/vc4+pY9axOlmCkJg7xCPwhI75y1cONgovhz+BEXOzolh5KZuGbGbj xe0wva5DLBeIg7EQFf+99pOS7Syby3Xpm6ZbswEFV0cllK4jf/QMjtfInxobuMoI0GV0bE5l WlRtPCK5FnbHwxi0wPNzB/5fwzJ77r6HgPrR0OkT0lWmbUyoOQARAQABtC1MYXVuY2hwYWQg UFBBIGZvciBjbG91ZCBpbml0IGRldmVsb3BtZW50IHRlYW2JAjgEEwECACIFAlbZRUICGwMG CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEAg9Bvvk0wTfHfcP/REK5N2s1JYc69qEa9ZN o6oi+A7l6AYw+ZY88O5TJe7F9otv5VXCIKSUT0Vsepjgf0mtXAgf/sb2lsJn/jp7tzgov3YH vSrkTkRydz8xcA87gwQKePuvTLxQpftF4flrBxgSueIn5O/tPrBOxLz7EVYBc78SKg9aj9L2 yUp+YuNevlwfZCTYeBb9r3FHaab2HcgkwqYch66+nKYfwiLuQ9NzXXm0Wn0JcEQ6pWvJscbj C9BdawWovfvMK5/YLfI6Btm7F4mIpQBdhSOUp/YXKmdvHpmwxMCN2QhqYK49SM7qE9aUDbJL arppSEBtlCLWhRBZYLTUna+BkuQ1bHz4St++XTR49Qd7vDERALpApDjB2dxPfMiBzCMwQQyq uy13exU8o2ETLg+dZSLfDTzrBNsBFmXlw8WW17nTISYdKeGKL+QdlUjpzdwUMMzHhAO8SmMH zjeSlDSRMXBJFAFSbCl7EwmMKa3yVX0zInT91fNllZ3iatAmtVdqVH/BFQfTIMH2ET7A8WzJ ZzVSuMRhqoKdr5AMcHuJGPUoVkVJHQA+NNvEiXSysF3faL7jmKapmUwrhpYYX2H8pf+VMu2e cLflKTI28dl+ZQ4Pl/aVsxrti/pzhdYy05Sn5ddtySyIkvo8L1cU5MWpbvSlFPkTstBUDLBf pb0uBy+g0oxJQg15 =uy53 -----END PGP PUBLIC KEY BLOCK----- apt_pipelining: os """ # noqa: E501 EXPECTED_REGEXES = [ r"deb http://badarchive.ubuntu.com/ubuntu [a-z]+ main restricted", r"deb-src http://badarchive.ubuntu.com/ubuntu [a-z]+ main restricted", r"deb http://badarchive.ubuntu.com/ubuntu [a-z]+ universe restricted", r"deb-src http://badarchive.ubuntu.com/ubuntu [a-z]+ universe restricted", r"deb http://badsecurity.ubuntu.com/ubuntu [a-z]+-security multiverse", r"deb-src http://badsecurity.ubuntu.com/ubuntu [a-z]+-security multiverse", ] TEST_KEYSERVER_KEY = "7260 0DB1 5B8E 4C8B 1964 B868 038A CC97 C660 A937" TEST_PPA_KEY = "3552 C902 B4DD F7BD 3842 1821 015D 28D7 4416 14D8" TEST_KEY = "1FF0 D853 5EF7 E719 E5C8 1B9C 083D 06FB E4D3 04DF" TEST_SIGNED_BY_KEY = "A2EB 2DEC 0BD7 519B 7B38 BE38 376A 290E C806 8B11" @pytest.mark.ci @pytest.mark.ubuntu @pytest.mark.user_data(USER_DATA) class TestApt: def get_keys(self, class_client: IntegrationInstance): """Return all keys in /etc/apt/trusted.gpg.d/ and /etc/apt/trusted.gpg in human readable format. Mimics the output of apt-key finger """ list_cmd = ' '.join(gpg.GPG_LIST) + ' ' keys = class_client.execute(list_cmd + cc_apt_configure.APT_LOCAL_KEYS) print(keys) files = class_client.execute( 'ls ' + cc_apt_configure.APT_TRUSTED_GPG_DIR) for file in files.split(): path = cc_apt_configure.APT_TRUSTED_GPG_DIR + file keys += class_client.execute(list_cmd + path) or '' return keys def test_sources_list(self, class_client: IntegrationInstance): """Integration test for the apt module's `sources_list` functionality. This test specifies a ``sources_list`` and then checks that (a) the expected number of sources.list entries is present, and (b) that each expected line appears in the file. (This is ported from `tests/cloud_tests/testcases/modules/apt_configure_sources_list.yaml`.) """ sources_list = class_client.read_from_file('/etc/apt/sources.list') assert 6 == len(sources_list.rstrip().split('\n')) for expected_re in EXPECTED_REGEXES: assert re.search(expected_re, sources_list) is not None def test_apt_conf(self, class_client: IntegrationInstance): """Test the apt conf functionality. Ported from tests/cloud_tests/testcases/modules/apt_configure_conf.py """ apt_config = class_client.read_from_file( '/etc/apt/apt.conf.d/94cloud-init-config' ) assert 'Assume-Yes "true";' in apt_config assert 'Fix-Broken "true";' in apt_config def test_apt_proxy(self, class_client: IntegrationInstance): """Test the apt proxy functionality. Ported from tests/cloud_tests/testcases/modules/apt_configure_proxy.py """ out = class_client.read_from_file( '/etc/apt/apt.conf.d/90cloud-init-aptproxy') assert 'Acquire::http::Proxy "http://proxy.internal:3128";' in out assert 'Acquire::http::Proxy "http://squid.internal:3128";' in out assert 'Acquire::ftp::Proxy "ftp://squid.internal:3128";' in out assert 'Acquire::https::Proxy "https://squid.internal:3128";' in out def test_ppa_source(self, class_client: IntegrationInstance): """Test the apt ppa functionality. Ported from tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.py """ release = ImageSpecification.from_os_image().release ppa_path_contents = class_client.read_from_file( '/etc/apt/sources.list.d/' 'simplestreams-dev-ubuntu-trunk-{}.list'.format(release) ) assert ( 'http://ppa.launchpad.net/simplestreams-dev/trunk/ubuntu' ) in ppa_path_contents assert TEST_PPA_KEY in self.get_keys(class_client) def test_signed_by(self, class_client: IntegrationInstance): """Test the apt signed-by functionality. """ release = ImageSpecification.from_os_image().release source = ( "deb [signed-by=/etc/apt/cloud-init.gpg.d/test_signed_by.gpg] " "http://ppa.launchpad.net/juju/stable/ubuntu" " {} main".format(release)) print(class_client.execute('cat /var/log/cloud-init.log')) path_contents = class_client.read_from_file( '/etc/apt/sources.list.d/test_signed_by.list') assert path_contents == source key = class_client.execute( 'gpg --no-default-keyring --with-fingerprint --list-keys ' '--keyring /etc/apt/cloud-init.gpg.d/test_signed_by.gpg') assert TEST_SIGNED_BY_KEY in key def test_bad_key(self, class_client: IntegrationInstance): """Test the apt signed-by functionality. """ with pytest.raises(OSError): class_client.read_from_file( '/etc/apt/trusted.list.d/test_bad_key.gpg') def test_key(self, class_client: IntegrationInstance): """Test the apt key functionality. Ported from tests/cloud_tests/testcases/modules/apt_configure_sources_key.py """ test_archive_contents = class_client.read_from_file( '/etc/apt/sources.list.d/test_key.list' ) assert ( 'http://ppa.launchpad.net/cloud-init-dev/test-archive/ubuntu' ) in test_archive_contents assert TEST_KEY in self.get_keys(class_client) def test_keyserver(self, class_client: IntegrationInstance): """Test the apt keyserver functionality. Ported from tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.py """ test_keyserver_contents = class_client.read_from_file( '/etc/apt/sources.list.d/test_keyserver.list' ) assert ( 'http://ppa.launchpad.net/cloud-init-raharper/curtin-dev/ubuntu' ) in test_keyserver_contents assert TEST_KEYSERVER_KEY in self.get_keys(class_client) def test_os_pipelining(self, class_client: IntegrationInstance): """Test 'os' settings does not write apt config file. Ported from tests/cloud_tests/testcases/modules/apt_pipelining_os.py """ conf_exists = class_client.execute( 'test -f /etc/apt/apt.conf.d/90cloud-init-pipelining' ).ok assert conf_exists is False _DEFAULT_DATA = """\ #cloud-config apt: primary: - arches: - default {uri} security: - arches: - default """ DEFAULT_DATA = _DEFAULT_DATA.format(uri='') @pytest.mark.ubuntu @pytest.mark.user_data(DEFAULT_DATA) class TestDefaults: @pytest.mark.openstack def test_primary_on_openstack(self, class_client: IntegrationInstance): """Test apt default primary source on openstack. When no uri is provided. """ zone = class_client.execute('cloud-init query v1.availability_zone') sources_list = class_client.read_from_file('/etc/apt/sources.list') assert '{}.clouds.archive.ubuntu.com'.format(zone) in sources_list def test_security(self, class_client: IntegrationInstance): """Test apt default security sources. Ported from tests/cloud_tests/testcases/modules/apt_configure_security.py """ sources_list = class_client.read_from_file('/etc/apt/sources.list') # 3 lines from main, universe, and multiverse assert 3 == sources_list.count('deb http://security.ubuntu.com/ubuntu') assert 3 == sources_list.count( '# deb-src http://security.ubuntu.com/ubuntu' ) DEFAULT_DATA_WITH_URI = _DEFAULT_DATA.format( uri='uri: "http://something.random.invalid/ubuntu"' ) @pytest.mark.user_data(DEFAULT_DATA_WITH_URI) def test_default_primary_with_uri(client: IntegrationInstance): """Test apt default primary sources. Ported from tests/cloud_tests/testcases/modules/apt_configure_primary.py """ sources_list = client.read_from_file('/etc/apt/sources.list') assert 'archive.ubuntu.com' not in sources_list assert 'something.random.invalid' in sources_list DISABLED_DATA = """\ #cloud-config apt: disable_suites: - $RELEASE - $RELEASE-updates - $RELEASE-backports - $RELEASE-security apt_pipelining: false """ @pytest.mark.ubuntu @pytest.mark.user_data(DISABLED_DATA) class TestDisabled: def test_disable_suites(self, class_client: IntegrationInstance): """Test disabling of apt suites. Ported from tests/cloud_tests/testcases/modules/apt_configure_disable_suites.py """ sources_list = class_client.execute( "cat /etc/apt/sources.list | grep -v '^#'" ).strip() assert '' == sources_list def test_disable_apt_pipelining(self, class_client: IntegrationInstance): """Test disabling of apt pipelining. Ported from tests/cloud_tests/testcases/modules/apt_pipelining_disable.py """ conf = class_client.read_from_file( '/etc/apt/apt.conf.d/90cloud-init-pipelining' ) assert 'Acquire::http::Pipeline-Depth "0";' in conf