Blob Blame History Raw
# This file is part of cloud-init. See LICENSE file for license information.

""" test_handler_apt_configure_sources_list
Test templating of sources list
"""
import logging
import os
import shutil
import tempfile
from unittest import mock

from cloudinit import cloud
from cloudinit import distros
from cloudinit import helpers
from cloudinit import templater
from cloudinit import subp
from cloudinit import util

from cloudinit.config import cc_apt_configure
from cloudinit.sources import DataSourceNone

from cloudinit.distros.debian import Distro

from cloudinit.tests import helpers as t_help

LOG = logging.getLogger(__name__)

YAML_TEXT_CUSTOM_SL = """
apt_mirror: http://archive.ubuntu.com/ubuntu/
apt_custom_sources_list: |
    ## template:jinja
    ## Note, this file is written by cloud-init on first boot of an instance
    ## modifications made here will not survive a re-bundle.
    ## if you wish to make changes you can:
    ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg
    ##     or do the same in user-data
    ## b.) add sources in /etc/apt/sources.list.d
    ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl

    # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
    # newer versions of the distribution.
    deb {{mirror}} {{codename}} main restricted
    deb-src {{mirror}} {{codename}} main restricted
    # FIND_SOMETHING_SPECIAL
"""

EXPECTED_CONVERTED_CONTENT = (
    """## Note, this file is written by cloud-init on first boot of an instance
## modifications made here will not survive a re-bundle.
## if you wish to make changes you can:
## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg
##     or do the same in user-data
## b.) add sources in /etc/apt/sources.list.d
## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl

# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://archive.ubuntu.com/ubuntu/ fakerelease main restricted
deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted
# FIND_SOMETHING_SPECIAL
""")


class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
    """TestAptSourceConfigSourceList
    Main Class to test sources list rendering
    """
    def setUp(self):
        super(TestAptSourceConfigSourceList, self).setUp()
        self.subp = subp.subp
        self.new_root = tempfile.mkdtemp()
        self.addCleanup(shutil.rmtree, self.new_root)

        rpatcher = mock.patch("cloudinit.util.lsb_release")
        get_rel = rpatcher.start()
        get_rel.return_value = {'codename': "fakerelease"}
        self.addCleanup(rpatcher.stop)
        apatcher = mock.patch("cloudinit.util.get_dpkg_architecture")
        get_arch = apatcher.start()
        get_arch.return_value = 'amd64'
        self.addCleanup(apatcher.stop)

    def _get_cloud(self, distro, metadata=None):
        self.patchUtils(self.new_root)
        paths = helpers.Paths({})
        cls = distros.fetch(distro)
        mydist = cls(distro, {}, paths)
        myds = DataSourceNone.DataSourceNone({}, mydist, paths)
        if metadata:
            myds.metadata.update(metadata)
        return cloud.Cloud(myds, paths, {}, mydist, None)

    def apt_source_list(self, distro, mirror, mirrorcheck=None):
        """apt_source_list
        Test rendering of a source.list from template for a given distro
        """
        if mirrorcheck is None:
            mirrorcheck = mirror

        if isinstance(mirror, list):
            cfg = {'apt_mirror_search': mirror}
        else:
            cfg = {'apt_mirror': mirror}

        mycloud = self._get_cloud(distro)

        with mock.patch.object(util, 'write_file') as mockwf:
            with mock.patch.object(util, 'load_file',
                                   return_value="faketmpl") as mocklf:
                with mock.patch.object(os.path, 'isfile',
                                       return_value=True) as mockisfile:
                    with mock.patch.object(
                        templater, 'render_string',
                            return_value='fake') as mockrnd:
                        with mock.patch.object(util, 'rename'):
                            cc_apt_configure.handle("test", cfg, mycloud,
                                                    LOG, None)

        mockisfile.assert_any_call(
            ('/etc/cloud/templates/sources.list.%s.tmpl' % distro))
        mocklf.assert_any_call(
            ('/etc/cloud/templates/sources.list.%s.tmpl' % distro))
        mockrnd.assert_called_once_with('faketmpl',
                                        {'RELEASE': 'fakerelease',
                                         'PRIMARY': mirrorcheck,
                                         'MIRROR': mirrorcheck,
                                         'SECURITY': mirrorcheck,
                                         'codename': 'fakerelease',
                                         'primary': mirrorcheck,
                                         'mirror': mirrorcheck,
                                         'security': mirrorcheck})
        mockwf.assert_called_once_with('/etc/apt/sources.list', 'fake',
                                       mode=0o644)

    def test_apt_v1_source_list_debian(self):
        """Test rendering of a source.list from template for debian"""
        self.apt_source_list('debian', 'http://httpredir.debian.org/debian')

    def test_apt_v1_source_list_ubuntu(self):
        """Test rendering of a source.list from template for ubuntu"""
        self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/')

    @staticmethod
    def myresolve(name):
        """Fake util.is_resolvable for mirrorfail tests"""
        if name == "does.not.exist":
            print("Faking FAIL for '%s'" % name)
            return False
        else:
            print("Faking SUCCESS for '%s'" % name)
            return True

    def test_apt_v1_srcl_debian_mirrorfail(self):
        """Test rendering of a source.list from template for debian"""
        with mock.patch.object(util, 'is_resolvable',
                               side_effect=self.myresolve) as mockresolve:
            self.apt_source_list('debian',
                                 ['http://does.not.exist',
                                  'http://httpredir.debian.org/debian'],
                                 'http://httpredir.debian.org/debian')
        mockresolve.assert_any_call("does.not.exist")
        mockresolve.assert_any_call("httpredir.debian.org")

    def test_apt_v1_srcl_ubuntu_mirrorfail(self):
        """Test rendering of a source.list from template for ubuntu"""
        with mock.patch.object(util, 'is_resolvable',
                               side_effect=self.myresolve) as mockresolve:
            self.apt_source_list('ubuntu',
                                 ['http://does.not.exist',
                                  'http://archive.ubuntu.com/ubuntu/'],
                                 'http://archive.ubuntu.com/ubuntu/')
        mockresolve.assert_any_call("does.not.exist")
        mockresolve.assert_any_call("archive.ubuntu.com")

    def test_apt_v1_srcl_custom(self):
        """Test rendering from a custom source.list template"""
        cfg = util.load_yaml(YAML_TEXT_CUSTOM_SL)
        mycloud = self._get_cloud('ubuntu')

        # the second mock restores the original subp
        with mock.patch.object(util, 'write_file') as mockwrite:
            with mock.patch.object(subp, 'subp', self.subp):
                with mock.patch.object(Distro, 'get_primary_arch',
                                       return_value='amd64'):
                    cc_apt_configure.handle("notimportant", cfg, mycloud,
                                            LOG, None)

        mockwrite.assert_called_once_with(
            '/etc/apt/sources.list',
            EXPECTED_CONVERTED_CONTENT,
            mode=420)


# vi: ts=4 expandtab