# This file is part of cloud-init. See LICENSE file for license information.

""" test_apt_custom_sources_list
Test templating of custom sources list
"""
import stat

import pytest

from cloudinit import subp, util
from cloudinit.config import cc_apt_configure
from cloudinit.distros.debian import Distro
from tests.unittests.util import get_cloud

TARGET = "/"

# Input and expected output for the custom template
EXAMPLE_TMPL = """\
## template:jinja
deb {{mirror}} {{codename}} main restricted
deb-src {{mirror}} {{codename}} main restricted
deb {{mirror}} {{codename}}-updates universe restricted
deb {{security}} {{codename}}-security multiverse
"""

EXAMPLE_TMPL_DEB822 = """\
## template:jinja
# Generated by cloud-init
Types: deb deb-src
URIs: {{mirror}}
Suites: {{codename}} {{codename}}-updates
Components: main restricted

# Security section
Types: deb deb-src
URIs: {{security}}
Suites: {{codename}}-security
Components: main restricted
"""

YAML_TEXT_CUSTOM_SL = """
apt:
  primary:
    - arches: [default]
      uri: http://test.ubuntu.com/ubuntu/
  security:
    - arches: [default]
      uri: http://testsec.ubuntu.com/ubuntu/
  sources_list: |

      # Note, this file is written by cloud-init at install time.
      deb $MIRROR $RELEASE main restricted
      deb-src $MIRROR $RELEASE main restricted
      deb $PRIMARY $RELEASE universe restricted
      deb $SECURITY $RELEASE-security multiverse
      # FIND_SOMETHING_SPECIAL
"""

EXPECTED_CONVERTED_CONTENT = """
# Note, this file is written by cloud-init at install time.
deb http://test.ubuntu.com/ubuntu/ fakerel main restricted
deb-src http://test.ubuntu.com/ubuntu/ fakerel main restricted
deb http://test.ubuntu.com/ubuntu/ fakerel universe restricted
deb http://testsec.ubuntu.com/ubuntu/ fakerel-security multiverse
"""

YAML_TEXT_CUSTOM_SL_DEB822 = """
apt:
  primary:
    - arches: [default]
      uri: http://test.ubuntu.com/ubuntu/
  security:
    - arches: [default]
      uri: http://testsec.ubuntu.com/ubuntu/
  sources_list: |

      # custom deb822 sources_list deb822
      Types: deb deb-src
      URIs: $PRIMARY
      Suites: $RELEASE $RELEASE-updates
      Components: main restricted

      Types: deb deb-src
      URIs: $SECURITY
      Suites: $RELEASE-security
      Components: main restricted
"""

EXPECTED_CONVERTED_CONTENT_DEB822 = """
# custom deb822 sources_list deb822
Types: deb deb-src
URIs: http://test.ubuntu.com/ubuntu/
Suites: fakerel fakerel-updates
Components: main restricted

Types: deb deb-src
URIs: http://testsec.ubuntu.com/ubuntu/
Suites: fakerel-security
Components: main restricted
"""

# mocked to be independent to the unittest system

EXPECTED_BASE_CONTENT = """\
deb http://archive.ubuntu.com/ubuntu/ fakerel main restricted
deb-src http://archive.ubuntu.com/ubuntu/ fakerel main restricted
deb http://archive.ubuntu.com/ubuntu/ fakerel-updates universe restricted
deb http://security.ubuntu.com/ubuntu/ fakerel-security multiverse
"""

EXPECTED_MIRROR_CONTENT = """\
deb http://test.ubuntu.com/ubuntu/ fakerel main restricted
deb-src http://test.ubuntu.com/ubuntu/ fakerel main restricted
deb http://test.ubuntu.com/ubuntu/ fakerel-updates main restricted
deb http://test.ubuntu.com/ubuntu/ fakerel-security main restricted
"""

EXPECTED_BASE_CONTENT_DEB822 = """\
Types: deb deb-src
URIs: http://archive.ubuntu.com/ubuntu/
Suites: fakerel fakerel-updates
Components: main restricted

# Security section
Types: deb deb-src
URIs: http://security.ubuntu.com/ubuntu/
Suites: fakerel-security
Components: main restricted
"""

EXPECTED_PRIMSEC_CONTENT = """\
deb http://test.ubuntu.com/ubuntu/ fakerel main restricted
deb-src http://test.ubuntu.com/ubuntu/ fakerel main restricted
deb http://test.ubuntu.com/ubuntu/ fakerel-updates universe restricted
deb http://testsec.ubuntu.com/ubuntu/ fakerel-security multiverse
"""

EXPECTED_PRIMSEC_CONTENT_DEB822 = """# Generated by cloud-init
Types: deb deb-src
URIs: http://test.ubuntu.com/ubuntu/
Suites: fakerel fakerel-updates
Components: main restricted

# Security section
Types: deb deb-src
URIs: http://testsec.ubuntu.com/ubuntu/
Suites: fakerel-security
Components: main restricted
"""


@pytest.mark.usefixtures("fake_filesystem")
class TestAptSourceConfigSourceList:
    """TestAptSourceConfigSourceList - Class to test sources list rendering"""

    @pytest.fixture(autouse=True)
    def common_mocks(self, mocker):
        self.subp = mocker.patch.object(
            subp,
            "subp",
            return_value=("PPID   PID", ""),
        )
        lsb = mocker.patch("cloudinit.util.lsb_release")
        lsb.return_value = {"codename": "fakerel"}
        m_arch = mocker.patch("cloudinit.util.get_dpkg_architecture")
        m_arch.return_value = "amd64"
        mocker.patch("cloudinit.config.cc_apt_configure._ensure_dependencies")
        self.deb822 = mocker.patch.object(
            cc_apt_configure.features, "APT_DEB822_SOURCE_LIST_FILE", True
        )
        mocker.patch.object(
            cc_apt_configure,
            "get_apt_cfg",
            return_value={
                "sourcelist": "/etc/apt/sources.list",
                "sourceparts": "/etc/apt/sources.list.d/",
            },
        )

    @pytest.mark.parametrize(
        "distro,template_present",
        (("ubuntu", True), ("debian", True), ("rhel", False)),
    )
    def test_apt_v3_empty_cfg_source_list_by_distro(
        self, distro, template_present, mocker, tmpdir
    ):
        """Template based on distro, empty config relies on mirror default."""
        template = f"/etc/cloud/templates/sources.list.{distro}.deb822.tmpl"
        if template_present:
            util.write_file(template, EXAMPLE_TMPL_DEB822)

        mycloud = get_cloud(distro)
        mock_shouldcfg = mocker.patch.object(
            cc_apt_configure,
            "_should_configure_on_empty_apt",
            return_value=(True, "test"),
        )
        cc_apt_configure.handle("test", {"apt": {}}, mycloud, None)

        sources_file = tmpdir.join(f"/etc/apt/sources.list.d/{distro}.sources")
        if template_present:
            assert (
                "# Generated by cloud-init\n" + EXPECTED_BASE_CONTENT_DEB822
                == sources_file.read()
            )
            assert 0o644 == stat.S_IMODE(sources_file.stat().mode)
        else:
            assert (
                sources_file.exists() is False
            ), f"Unexpected file found: {sources_file}"

        assert 1 == mock_shouldcfg.call_count

    def test_apt_v3_source_list_ubuntu_snappy(self, mocker):
        """test_apt_v3_source_list_ubuntu_snappy - without custom sources or
        parms"""
        cfg = {"apt": {}}
        mycloud = get_cloud()

        mock_writefile = mocker.patch.object(util, "write_file")
        mock_issnappy = mocker.patch.object(util, "system_is_snappy")
        mock_issnappy.return_value = True
        cc_apt_configure.handle("test", cfg, mycloud, None)
        mock_writefile.assert_not_called()
        assert 1 == mock_issnappy.call_count

    @pytest.mark.parametrize(
        "deb822,tmpl_file,tmpl_content,apt_file,expected",
        (
            (
                True,
                "/etc/cloud/templates/sources.list.ubuntu.deb822.tmpl",
                EXAMPLE_TMPL_DEB822,
                "/etc/apt/sources.list.d/ubuntu.sources",
                EXPECTED_PRIMSEC_CONTENT_DEB822,
            ),
            (
                False,
                "/etc/cloud/templates/sources.list.ubuntu.tmpl",
                EXAMPLE_TMPL,
                "/etc/apt/sources.list",
                EXPECTED_PRIMSEC_CONTENT,
            ),
        ),
    )
    def test_apt_v3_source_list_psm_deb822_feature_aware(
        self,
        deb822,
        tmpl_file,
        tmpl_content,
        apt_file,
        expected,
        tmpdir,
        mocker,
    ):
        """test_apt_v3_source_list_psm - Test specifying prim+sec mirrors

        Assert APT_DEB822_SOURCE_LIST_FILE is taken into account when
        determining which sources.list.tmpl source file to read.
        """
        pm = "http://test.ubuntu.com/ubuntu/"
        sm = "http://testsec.ubuntu.com/ubuntu/"
        cfg = {
            "preserve_sources_list": False,
            "primary": [{"arches": ["default"], "uri": pm}],
            "security": [{"arches": ["default"], "uri": sm}],
        }

        self.deb822 = mocker.patch.object(
            cc_apt_configure.features, "APT_DEB822_SOURCE_LIST_FILE", deb822
        )

        util.write_file(tmpl_file, tmpl_content)
        mycloud = get_cloud("ubuntu")
        cc_apt_configure.handle("test", {"apt": cfg}, mycloud, None)

        sources_file = tmpdir.join(apt_file)
        assert expected == sources_file.read()
        assert 0o644 == stat.S_IMODE(sources_file.stat().mode)

    @pytest.mark.parametrize(
        "deb822,cfg,apt_file,expected",
        (
            pytest.param(
                True,
                util.load_yaml(YAML_TEXT_CUSTOM_SL_DEB822),
                "/etc/apt/sources.list.d/debian.sources",
                EXPECTED_CONVERTED_CONTENT_DEB822,
                id="deb822_feature_deb822_sources_list_writes_deb822_source",
            ),
            pytest.param(
                True,
                util.load_yaml(YAML_TEXT_CUSTOM_SL),
                "/etc/apt/sources.list",
                EXPECTED_CONVERTED_CONTENT + "# FIND_SOMETHING_SPECIAL\n",
                id="deb822_feature_nondeb822_sources_list_writes_list_file",
            ),
            pytest.param(
                False,
                util.load_yaml(YAML_TEXT_CUSTOM_SL),
                "/etc/apt/sources.list",
                EXPECTED_CONVERTED_CONTENT + "# FIND_SOMETHING_SPECIAL\n",
                id="nodeb822_feature_nondeb822_sources_list_writes_list_file",
            ),
            pytest.param(
                False,
                util.load_yaml(YAML_TEXT_CUSTOM_SL_DEB822),
                "/etc/apt/sources.list.d/debian.sources",
                EXPECTED_CONVERTED_CONTENT_DEB822,
                id="nodeb822_feature_deb822_sources_list_writes_deb822_source",
            ),
        ),
    )
    def test_apt_v3_srcl_custom_deb822_feature_aware(
        self, deb822, cfg, apt_file, expected, mocker, tmpdir
    ):
        """test_apt_v3_srcl_custom - Test rendering a custom source template

        Also take into account deb822 feature flag to assert writing the
        appropriate deb822 /etc/apt/sources.list.d/*list or *source file based
        on content and deb822 feature flag.
        """
        mycloud = get_cloud("debian")

        self.deb822 = mocker.patch.object(
            cc_apt_configure.features, "APT_DEB822_SOURCE_LIST_FILE", deb822
        )
        mocker.patch.object(Distro, "get_primary_arch", return_value="amd64")
        cc_apt_configure.handle("notimportant", cfg, mycloud, None)
        sources_file = tmpdir.join(apt_file)
        assert expected == sources_file.read()
        assert 0o644 == stat.S_IMODE(sources_file.stat().mode)
        self.subp.assert_called_once_with(
            ["ps", "-o", "ppid,pid", "-C", "dirmngr", "-C", "gpg-agent"],
            capture=True,
            target=None,
            rcs=[0, 1],
        )
