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)

Conflict:(1) use util.load_file not util.load_text_file in
render_network_state().
(2)test format diff.

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 ++++++
 .../unittests/test_distros/test_netconfig.py  | 63 ++++++++++++++++++-
 2 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 7135ecf..c030e41 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -970,6 +970,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/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 3f82d54..69cc26f 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -528,12 +528,17 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
     def control_path(self):
         return '/etc/sysconfig/network'
 
-    def _apply_and_verify(self, apply_fn, config, expected_cfgs=None,
-                          bringup=False):
+    def _apply_and_verify(
+        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:
@@ -606,6 +611,58 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
                                V1_NET_CFG_IPV6,
                                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

