Your message dated Sat, 09 Nov 2024 10:51:02 +0000
with message-id 
<b0a29248bc631362ed06a8879f93b8cdae5414d0.ca...@adam-barratt.org.uk>
and subject line Closing bugs released with 12.8
has caused the Debian Bug report #1082746,
regarding bookworm-pu: package cloud-init/22.4.2-1+deb12u2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact ow...@bugs.debian.org
immediately.)


-- 
1082746: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1082746
Debian Bug Tracking System
Contact ow...@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian....@packages.debian.org
Usertags: pu
X-Debbugs-Cc: cloud-i...@packages.debian.org
Control: affects -1 + src:cloud-init

[ Reason ]

Cloud-init is a package typically built in to cloud VM images.  It provides
functionality to facilitate launch-time customization of VMs based on
user-defined configuration data provided to the VM by various supported
mechanisms.  This configuration data may contain network configuration, which
cloud-init translates from its custom representation to supported
configuration for one of several supported backends (e.g. ifupdown,
systemd-networkd, and netplan).  

The cloud-init package in bookworm contains a bug that prevents it from
generating valid systemd-networkd configuration from certain valid input.

[ Impact ]

Users are left with a broken IPv6 network configuration when attempting to
configure static dualstack networking.

[ Tests ]

* Unit test coverage in the upstream patch
* Repro case provided in the Debian bug

[ Risks ]

The fix has been in sid/trixie since early this year and is pretty well tested
at this point.  The patch cherry-picks cleanly to the bookworm version, and
passes testing, so the risk should be small.  Worst case scenario is that the
change introduces a new regression that breaks previously working network
configuration scenarios for cloud users.

[ Checklist ]
  [*] *all* changes are documented in the d/changelog
  [*] I reviewed all changes and I approve them
  [*] attach debdiff against the package in (old)stable
  [*] the issue is verified as fixed in unstable

[ Changes ]

Upstream fix is cherry-picked from
https://github.com/canonical/cloud-init/commit/f75be2ebe15b0dc78092fe47b1ef8d506607e9da
diff -Nru cloud-init-22.4.2/debian/changelog cloud-init-22.4.2/debian/changelog
--- cloud-init-22.4.2/debian/changelog  2024-05-28 16:23:38.000000000 -0400
+++ cloud-init-22.4.2/debian/changelog  2024-09-17 11:08:48.000000000 -0400
@@ -1,3 +1,9 @@
+cloud-init (22.4.2-1+deb12u2) bookworm; urgency=medium
+
+  * networkd: Add support for multiple [Route] sections (Closes: #1052535)
+
+ -- Noah Meyerhans <no...@debian.org>  Tue, 17 Sep 2024 11:08:48 -0400
+
 cloud-init (22.4.2-1+deb12u1) bookworm; urgency=medium
 
   * Add Conflicts/Replaces relationship on cloud-init-22.4.2
diff -Nru 
cloud-init-22.4.2/debian/patches/networkd_Add_support_for_multiple_Route_sections.patch
 
cloud-init-22.4.2/debian/patches/networkd_Add_support_for_multiple_Route_sections.patch
--- 
cloud-init-22.4.2/debian/patches/networkd_Add_support_for_multiple_Route_sections.patch
     1969-12-31 19:00:00.000000000 -0500
+++ 
cloud-init-22.4.2/debian/patches/networkd_Add_support_for_multiple_Route_sections.patch
     2024-09-17 11:08:48.000000000 -0400
@@ -0,0 +1,215 @@
+From f75be2ebe15b0dc78092fe47b1ef8d506607e9da Mon Sep 17 00:00:00 2001
+From: Nigel Kukard <nkuk...@lbsd.net>
+Date: Wed, 7 Dec 2022 23:29:52 +0000
+Subject: [PATCH] networkd: Add support for multiple [Route] sections (#1868)
+
+Networkd supports multiple [Route] sections within the same file.
+Currently all [Route] section tags are squashed into one and if there
+is a default gateway it means defining a device route is not possible
+as the target is set to the default gateway.
+
+This patch adds support for multiple [Route] sections allowing us to
+support device routes. This is done by tracking each route in the route
+list individually and ensuring the key-value pairs are maintained within
+their respective [Route] section. This both maintains backwards
+compatibility with previous behavior and allows the specification of
+routes with no destination IP, causing the destination to be added with
+a device target.
+---
+ cloudinit/net/networkd.py            | 51 ++++++++++++++++++++++---
+ tests/unittests/net/test_networkd.py | 57 +++++++++++++++++++++++++++-
+ 2 files changed, 101 insertions(+), 7 deletions(-)
+
+Index: cloud-init/cloudinit/net/networkd.py
+===================================================================
+--- cloud-init.orig/cloudinit/net/networkd.py
++++ cloud-init/cloudinit/net/networkd.py
+@@ -28,7 +28,7 @@ class CfgParser:
+                 "DHCPv4": [],
+                 "DHCPv6": [],
+                 "Address": [],
+-                "Route": [],
++                "Route": {},
+             }
+         )
+ 
+@@ -40,6 +40,22 @@ class CfgParser:
+                 self.conf_dict[k] = list(dict.fromkeys(self.conf_dict[k]))
+                 self.conf_dict[k].sort()
+ 
++    def update_route_section(self, sec, rid, key, val):
++        """
++        For each route section we use rid as a key, this allows us to isolate
++        this route from others on subsequent calls.
++        """
++        for k in self.conf_dict.keys():
++            if k == sec:
++                if rid not in self.conf_dict[k]:
++                    self.conf_dict[k][rid] = []
++                self.conf_dict[k][rid].append(key + "=" + str(val))
++                # remove duplicates from list
++                self.conf_dict[k][rid] = list(
++                    dict.fromkeys(self.conf_dict[k][rid])
++                )
++                self.conf_dict[k][rid].sort()
++
+     def get_final_conf(self):
+         contents = ""
+         for k, v in sorted(self.conf_dict.items()):
+@@ -50,6 +66,12 @@ class CfgParser:
+                     contents += "[" + k + "]\n"
+                     contents += e + "\n"
+                     contents += "\n"
++            elif k == "Route":
++                for n in sorted(v):
++                    contents += "[" + k + "]\n"
++                    for e in sorted(v[n]):
++                        contents += e + "\n"
++                        contents += "\n"
+             else:
+                 contents += "[" + k + "]\n"
+                 for e in sorted(v):
+@@ -112,7 +134,11 @@ class Renderer(renderer.Renderer):
+         if "mtu" in iface and iface["mtu"]:
+             cfg.update_section(sec, "MTUBytes", iface["mtu"])
+ 
+-    def parse_routes(self, conf, cfg: CfgParser):
++    def parse_routes(self, rid, conf, cfg: CfgParser):
++        """
++        Parse a route and use rid as a key in order to isolate the route from
++        others in the route dict.
++        """
+         sec = "Route"
+         route_cfg_map = {
+             "gateway": "Gateway",
+@@ -130,11 +156,12 @@ class Renderer(renderer.Renderer):
+                 continue
+             if k == "network":
+                 v += prefix
+-            cfg.update_section(sec, route_cfg_map[k], v)
++            cfg.update_route_section(sec, rid, route_cfg_map[k], v)
+ 
+     def parse_subnets(self, iface, cfg: CfgParser):
+         dhcp = "no"
+         sec = "Network"
++        rid = 0
+         for e in iface.get("subnets", []):
+             t = e["type"]
+             if t == "dhcp4" or t == "dhcp":
+@@ -149,7 +176,10 @@ class Renderer(renderer.Renderer):
+                     dhcp = "yes"
+             if "routes" in e and e["routes"]:
+                 for i in e["routes"]:
+-                    self.parse_routes(i, cfg)
++                    # Use "r" as a dict key prefix for this route to isolate
++                    # it from other sources of routes
++                    self.parse_routes(f"r{rid}", i, cfg)
++                    rid = rid + 1
+             if "address" in e:
+                 subnet_cfg_map = {
+                     "address": "Address",
+@@ -163,7 +193,12 @@ class Renderer(renderer.Renderer):
+                             v += "/" + str(e["prefix"])
+                         cfg.update_section("Address", subnet_cfg_map[k], v)
+                     elif k == "gateway":
+-                        cfg.update_section("Route", subnet_cfg_map[k], v)
++                        # Use "a" as a dict key prefix for this route to
++                        # isolate it from other sources of routes
++                        cfg.update_route_section(
++                            "Route", f"a{rid}", subnet_cfg_map[k], v
++                        )
++                        rid = rid + 1
+                     elif k == "dns_nameservers" or k == "dns_search":
+                         cfg.update_section(sec, subnet_cfg_map[k], " 
".join(v))
+ 
+@@ -280,8 +315,12 @@ class Renderer(renderer.Renderer):
+             dhcp = self.parse_subnets(iface, cfg)
+             self.parse_dns(iface, cfg, ns)
+ 
++            rid = 0
+             for route in ns.iter_routes():
+-                self.parse_routes(route, cfg)
++                # Use "c" as a dict key prefix for this route to isolate it
++                # from other sources of routes
++                self.parse_routes(f"c{rid}", route, cfg)
++                rid = rid + 1
+ 
+             if ns.version == 2:
+                 name: Optional[str] = iface["name"]
+Index: cloud-init/tests/unittests/net/test_networkd.py
+===================================================================
+--- cloud-init.orig/tests/unittests/net/test_networkd.py
++++ cloud-init/tests/unittests/net/test_networkd.py
+@@ -195,10 +195,54 @@ Domains=rgrunbla.github.beta.tailscale.n
+ 
+ [Route]
+ Gateway=10.0.0.1
++
++[Route]
+ Gateway=2a01:4f8:10a:19d2::2
+ 
+ """
+ 
++V2_CONFIG_MULTI_SUBNETS = """
++network:
++  version: 2
++  ethernets:
++    eth0:
++      addresses:
++        - 192.168.1.1/24
++        - fec0::1/64
++      gateway4: 192.168.254.254
++      gateway6: "fec0::ffff"
++      routes:
++        - to: 169.254.1.1/32
++        - to: "fe80::1/128"
++"""
++
++V2_CONFIG_MULTI_SUBNETS_RENDERED = """\
++[Address]
++Address=192.168.1.1/24
++
++[Address]
++Address=fec0::1/64
++
++[Match]
++Name=eth0
++
++[Network]
++DHCP=no
++
++[Route]
++Gateway=192.168.254.254
++
++[Route]
++Gateway=fec0::ffff
++
++[Route]
++Destination=169.254.1.1/32
++
++[Route]
++Destination=fe80::1/128
++
++"""
++
+ 
+ class TestNetworkdRenderState:
+     def _parse_network_state_from_config(self, config):
+@@ -307,5 +351,16 @@ class TestNetworkdRenderState:
+ 
+         assert rendered_content["eth0"] == V1_CONFIG_MULTI_SUBNETS_RENDERED
+ 
++    def test_networkd_render_v2_multi_subnets(self):
++        """
++        Ensure a device with multiple subnets gets correctly rendered.
++
++        Per systemd-networkd docs, [Route] can only contain a single instance
++        of Gateway.
++        """
++        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
++            ns = 
self._parse_network_state_from_config(V2_CONFIG_MULTI_SUBNETS)
++            renderer = networkd.Renderer()
++            rendered_content = renderer._render_content(ns)
+ 
+-# vi: ts=4 expandtab
++        assert rendered_content["eth0"] == V2_CONFIG_MULTI_SUBNETS_RENDERED
diff -Nru cloud-init-22.4.2/debian/patches/series 
cloud-init-22.4.2/debian/patches/series
--- cloud-init-22.4.2/debian/patches/series     2024-05-28 16:23:38.000000000 
-0400
+++ cloud-init-22.4.2/debian/patches/series     2024-09-17 11:08:48.000000000 
-0400
@@ -3,3 +3,4 @@
 0009-Drop-all-unused-extended-version-handling.patch
 0012-Fix-message-when-a-local-is-missing.patch
 0001-config-Support-APT-automated-mirror-selection.patch
+networkd_Add_support_for_multiple_Route_sections.patch

--- End Message ---
--- Begin Message ---
Source: release.debian.org
Version: 12.8

Hi,

Each of the updates tracked by these bugs was included in today's 12.8
bookworm point release.

Regards,

Adam

--- End Message ---

Reply via email to