From fa331315d22f4bbe33320485e89a02bb2f695fbf Mon Sep 17 00:00:00 2001
From: Ani Sinha <anisinha@redhat.com>
Date: Sat, 15 Feb 2025 01:54:31 +0530
Subject: [PATCH] net/sysconfig: do not remove all existing settings of
 /etc/sysconfig/network (#5991)

Reference:https://github.com/canonical/cloud-init/commit/fa331315d22f4bbe33320485e89a02bb2f695fbf
Conflict:use util.load_file not util.load_text_file in render_network_state().

In some distros, /etc/sysconfig/network may have important configurations that
are necessary for the instance to come up. For example, centos based distros
write NOZEROCONF=yes in /etc/sysconfig/network for some instances that require
zeroconf to be disabled. Removing these customizations would prevent the
instance to come up. So leave the customizations in /etc/sysconfig/network
intact except those that we are interested in.

Fixes GH-5990
Signed-off-by: Ani Sinha <anisinha@redhat.com>
---
 cloudinit/net/sysconfig.py                | 18 +++++++
 tests/unittests/distros/test_netconfig.py | 60 ++++++++++++++++++++++-
 2 files changed, 76 insertions(+), 2 deletions(-)

diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index ab241df..ce24f19 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -1035,6 +1035,24 @@ class Renderer(renderer.Renderer):
             if network_state.use_ipv6:
                 netcfg.append("NETWORKING_IPV6=yes")
                 netcfg.append("IPV6_AUTOCONF=no")
+
+            # if sysconfig file exists and is not empty, append rest of the
+            # file content, do not remove the exsisting customizations.
+            if os.path.exists(sysconfig_path):
+                for line in util.load_file(sysconfig_path).splitlines():
+                    if (
+                        not any(
+                            setting in line
+                            for setting in [
+                                "NETWORKING",
+                                "NETWORKING_IPV6",
+                                "IPV6_AUTOCONF",
+                            ]
+                        )
+                        and line not in _make_header().splitlines()
+                    ):
+                        netcfg.append(line)
+
             util.write_file(
                 sysconfig_path, "\n".join(netcfg) + "\n", file_mode
             )
diff --git a/tests/unittests/distros/test_netconfig.py b/tests/unittests/distros/test_netconfig.py
index 962ff7f..27ac636 100644
--- a/tests/unittests/distros/test_netconfig.py
+++ b/tests/unittests/distros/test_netconfig.py
@@ -697,12 +697,16 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
         return "/etc/sysconfig/network"
 
     def _apply_and_verify(
-        self, apply_fn, config, expected_cfgs=None, bringup=False
+        self,
+        apply_fn,
+        config,
+        expected_cfgs=None,
+        bringup=False,
+        tmpd=None,
     ):
         if not expected_cfgs:
             raise ValueError("expected_cfg must not be None")
 
-        tmpd = None
         with mock.patch("cloudinit.net.sysconfig.available") as m_avail:
             m_avail.return_value = True
             with self.reRooted(tmpd) as tmpd:
@@ -791,6 +795,58 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
             expected_cfgs=expected_cfgs.copy(),
         )
 
+    def test_sysconfig_network_no_overwite_ipv6_rh(self):
+        expected_cfgs = {
+            self.ifcfg_path("eth0"): dedent(
+                """\
+                BOOTPROTO=none
+                DEFROUTE=yes
+                DEVICE=eth0
+                IPV6ADDR=2607:f0d0:1002:0011::2/64
+                IPV6INIT=yes
+                IPV6_AUTOCONF=no
+                IPV6_DEFAULTGW=2607:f0d0:1002:0011::1
+                IPV6_FORCE_ACCEPT_RA=no
+                ONBOOT=yes
+                TYPE=Ethernet
+                USERCTL=no
+                """
+            ),
+            self.ifcfg_path("eth1"): dedent(
+                """\
+                BOOTPROTO=dhcp
+                DEVICE=eth1
+                ONBOOT=yes
+                TYPE=Ethernet
+                USERCTL=no
+                """
+            ),
+            self.control_path(): dedent(
+                """\
+                NETWORKING=yes
+                NETWORKING_IPV6=yes
+                IPV6_AUTOCONF=no
+                NOZEROCONF=yes
+                """
+            ),
+        }
+        tmpdir = self.tmp_dir()
+        file_mode = 0o644
+        # pre-existing config in /etc/sysconfig/network should not be removed
+        with self.reRooted(tmpdir) as tmpdir:
+            util.write_file(
+                self.control_path(),
+                "".join("NOZEROCONF=yes") + "\n",
+                file_mode,
+            )
+
+        self._apply_and_verify(
+            self.distro.apply_network_config,
+            V1_NET_CFG_IPV6,
+            expected_cfgs=expected_cfgs.copy(),
+            tmpd=tmpdir,
+        )
+
     def test_vlan_render_unsupported(self):
         """Render officially unsupported vlan names."""
         cfg = {
-- 
2.33.0


