summary refs log tree commit diff
diff options
context:
space:
mode:
authorJean-François Roche <jfroche@pyxel.be>2023-04-25 13:33:29 +0200
committerGitHub <noreply@github.com>2023-04-25 13:33:29 +0200
commit25671114cd9266e27b2609d94babdc66f52357a6 (patch)
tree2c3f408c51548552779bd8cf64417e28effb4706
parentc1e467b13c66c71a00e7fd12bcd168be32d55cb2 (diff)
downloadnixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.tar
nixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.tar.gz
nixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.tar.bz2
nixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.tar.lz
nixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.tar.xz
nixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.tar.zst
nixpkgs-25671114cd9266e27b2609d94babdc66f52357a6.zip
cloud-init: add udhcpc support (#226216)
* cloud-init: 22.4 -> 23.1.1

* cloud-init: add udhcpc support

Cloud-init use as dhcp client, dhclient, which is coming from the unmaintained package, isc-dhcp-client (refer https://www.isc.org/dhcp/) which ended support in 2022. dhclient is deprecated in nixos

Add patch to use `udhcpc` dhcp client coming from busybox instead.

PR based on #226173

refs #215571

upstream PR: https://github.com/canonical/cloud-init/pull/2125
-rw-r--r--nixos/modules/services/system/cloud-init.nix1
-rw-r--r--pkgs/tools/virtualization/cloud-init/0001-add-nixos-support.patch8
-rw-r--r--pkgs/tools/virtualization/cloud-init/0002-Add-Udhcpc-support.patch222
-rw-r--r--pkgs/tools/virtualization/cloud-init/default.nix20
4 files changed, 240 insertions, 11 deletions
diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index d75070dea43..82506596bc7 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -10,6 +10,7 @@ let cfg = config.services.cloud-init;
       openssh
       shadow
       util-linux
+      busybox
     ] ++ optional cfg.btrfs.enable btrfs-progs
       ++ optional cfg.ext4.enable e2fsprogs
     ;
diff --git a/pkgs/tools/virtualization/cloud-init/0001-add-nixos-support.patch b/pkgs/tools/virtualization/cloud-init/0001-add-nixos-support.patch
index 81ff2ef73ce..f79e3dda849 100644
--- a/pkgs/tools/virtualization/cloud-init/0001-add-nixos-support.patch
+++ b/pkgs/tools/virtualization/cloud-init/0001-add-nixos-support.patch
@@ -1,11 +1,11 @@
 diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
-index 4a468cf8..c60c899b 100644
+index b82852e1..c998b21e 100644
 --- a/cloudinit/distros/__init__.py
 +++ b/cloudinit/distros/__init__.py
-@@ -55,6 +55,7 @@ OSFAMILIES = {
-         "virtuozzo",
+@@ -74,6 +74,7 @@ OSFAMILIES = {
      ],
-     "suse": ["opensuse", "sles"],
+     "openEuler": ["openEuler"],
+     "OpenCloudOS": ["OpenCloudOS", "TencentOS"],
 +    "nixos": ["nixos"],
  }
  
diff --git a/pkgs/tools/virtualization/cloud-init/0002-Add-Udhcpc-support.patch b/pkgs/tools/virtualization/cloud-init/0002-Add-Udhcpc-support.patch
new file mode 100644
index 00000000000..ef169483769
--- /dev/null
+++ b/pkgs/tools/virtualization/cloud-init/0002-Add-Udhcpc-support.patch
@@ -0,0 +1,222 @@
+diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py
+index a9a1c980..2d83089b 100644
+--- a/cloudinit/net/dhcp.py
++++ b/cloudinit/net/dhcp.py
+@@ -14,12 +14,48 @@ from io import StringIO
+ 
+ import configobj
+ 
+-from cloudinit import subp, util
++from cloudinit import subp, util, temp_utils
+ from cloudinit.net import find_fallback_nic, get_devicelist
+ 
+ LOG = logging.getLogger(__name__)
+ 
+ NETWORKD_LEASES_DIR = "/run/systemd/netif/leases"
++UDHCPC_SCRIPT = """#!/bin/sh
++log() {
++    echo "udhcpc[$PPID]" "$interface: $2"
++}
++
++[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
++
++case $1 in
++    bound|renew)
++    cat <<JSON > "$LEASE_FILE"
++{
++    "interface": "$interface",
++    "fixed-address": "$ip",
++    "subnet-mask": "$subnet",
++    "routers": "${router%% *}",
++    "static_routes" : "${staticroutes}"
++}
++JSON
++    ;;
++
++    deconfig)
++    log err "Not supported"
++    exit 1
++    ;;
++
++    leasefail | nak)
++    log err "configuration failed: $1: $message"
++    exit 1
++    ;;
++
++    *)
++    echo "$0: Unknown udhcpc command: $1" >&2
++    exit 1
++    ;;
++esac
++"""
+ 
+ 
+ class NoDHCPLeaseError(Exception):
+@@ -43,12 +79,14 @@ class NoDHCPLeaseMissingDhclientError(NoDHCPLeaseError):
+ 
+ 
+ def maybe_perform_dhcp_discovery(nic=None, dhcp_log_func=None, tmp_dir=None):
+-    """Perform dhcp discovery if nic valid and dhclient command exists.
++    """Perform dhcp discovery if nic valid and dhclient or udhcpc command
++    exists.
+ 
+     If the nic is invalid or undiscoverable or dhclient command is not found,
+     skip dhcp_discovery and return an empty dict.
+ 
+-    @param nic: Name of the network interface we want to run dhclient on.
++    @param nic: Name of the network interface we want to run the dhcp client
++        on.
+     @param dhcp_log_func: A callable accepting the dhclient output and error
+         streams.
+     @param tmp_dir: Tmp dir with exec permissions.
+@@ -66,11 +104,16 @@ def maybe_perform_dhcp_discovery(nic=None, dhcp_log_func=None, tmp_dir=None):
+             "Skip dhcp_discovery: nic %s not found in get_devicelist.", nic
+         )
+         raise NoDHCPLeaseInterfaceError()
++    udhcpc_path = subp.which("udhcpc")
++    if udhcpc_path:
++        return dhcp_udhcpc_discovery(udhcpc_path, nic, dhcp_log_func)
+     dhclient_path = subp.which("dhclient")
+-    if not dhclient_path:
+-        LOG.debug("Skip dhclient configuration: No dhclient command found.")
+-        raise NoDHCPLeaseMissingDhclientError()
+-    return dhcp_discovery(dhclient_path, nic, dhcp_log_func)
++    if dhclient_path:
++        return dhcp_discovery(dhclient_path, nic, dhcp_log_func)
++    LOG.debug(
++        "Skip dhclient configuration: No dhclient or udhcpc command found."
++    )
++    raise NoDHCPLeaseMissingDhclientError()
+ 
+ 
+ def parse_dhcp_lease_file(lease_file):
+@@ -107,6 +150,61 @@ def parse_dhcp_lease_file(lease_file):
+     return dhcp_leases
+ 
+ 
++def dhcp_udhcpc_discovery(udhcpc_cmd_path, interface, dhcp_log_func=None):
++    """Run udhcpc on the interface without scripts or filesystem artifacts.
++
++    @param udhcpc_cmd_path: Full path to the udhcpc used.
++    @param interface: Name of the network interface on which to dhclient.
++    @param dhcp_log_func: A callable accepting the dhclient output and error
++        streams.
++
++    @return: A list of dicts of representing the dhcp leases parsed from the
++        dhclient.lease file or empty list.
++    """
++    LOG.debug("Performing a dhcp discovery on %s", interface)
++
++    tmp_dir = temp_utils.get_tmp_ancestor(needs_exe=True)
++    lease_file = os.path.join(tmp_dir, interface + ".lease.json")
++    with contextlib.suppress(FileNotFoundError):
++        os.remove(lease_file)
++
++    # udhcpc needs the interface up to send initial discovery packets.
++    # Generally dhclient relies on dhclient-script PREINIT action to bring the
++    # link up before attempting discovery. Since we are using -sf /bin/true,
++    # we need to do that "link up" ourselves first.
++    subp.subp(["ip", "link", "set", "dev", interface, "up"], capture=True)
++    udhcpc_script = os.path.join(tmp_dir, "udhcpc_script")
++    util.write_file(udhcpc_script, UDHCPC_SCRIPT, 0o755)
++    cmd = [
++        udhcpc_cmd_path,
++        "-O",
++        "staticroutes",
++        "-i",
++        interface,
++        "-s",
++        udhcpc_script,
++        "-n",  # Exit if lease is not obtained
++        "-q",  # Exit after obtaining lease
++        "-f",  # Run in foreground
++        "-v",
++    ]
++
++    out, err = subp.subp(
++        cmd, update_env={"LEASE_FILE": lease_file}, capture=True
++    )
++
++    if dhcp_log_func is not None:
++        dhcp_log_func(out, err)
++    lease_json = util.load_json(util.load_file(lease_file))
++    static_routes = lease_json["static_routes"].split()
++    if static_routes:
++        # format: dest1/mask gw1 ... destn/mask gwn
++        lease_json["static_routes"] = [
++            i for i in zip(static_routes[::2], static_routes[1::2])
++        ]
++    return [lease_json]
++
++
+ def dhcp_discovery(dhclient_cmd_path, interface, dhcp_log_func=None):
+     """Run dhclient on the interface without scripts or filesystem artifacts.
+ 
+diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py
+index 40340553..8913cf65 100644
+--- a/tests/unittests/net/test_dhcp.py
++++ b/tests/unittests/net/test_dhcp.py
+@@ -12,6 +12,7 @@ from cloudinit.net.dhcp import (
+     NoDHCPLeaseError,
+     NoDHCPLeaseInterfaceError,
+     NoDHCPLeaseMissingDhclientError,
++    dhcp_udhcpc_discovery,
+     dhcp_discovery,
+     maybe_perform_dhcp_discovery,
+     networkd_load_leases,
+@@ -334,6 +335,43 @@ class TestDHCPParseStaticRoutes(CiTestCase):
+         )
+ 
+ 
++class TestUDHCPCDiscoveryClean(CiTestCase):
++    maxDiff = None
++
++    @mock.patch("cloudinit.net.dhcp.os.remove")
++    @mock.patch("cloudinit.net.dhcp.subp.subp")
++    @mock.patch("cloudinit.util.load_json")
++    @mock.patch("cloudinit.util.load_file")
++    @mock.patch("cloudinit.util.write_file")
++    def test_udhcpc_discovery(
++        self, m_write_file, m_load_file, m_loadjson, m_subp, m_remove
++    ):
++        """dhcp_discovery waits for the presence of pidfile and dhcp.leases."""
++        m_subp.return_value = ("", "")
++        m_loadjson.return_value = {
++            "interface": "eth9",
++            "fixed-address": "192.168.2.74",
++            "subnet-mask": "255.255.255.0",
++            "routers": "192.168.2.1",
++            "static_routes": "10.240.0.1/32 0.0.0.0 0.0.0.0/0 10.240.0.1",
++        }
++        self.assertEqual(
++            [
++                {
++                    "fixed-address": "192.168.2.74",
++                    "interface": "eth9",
++                    "routers": "192.168.2.1",
++                    "static_routes": [
++                        ("10.240.0.1/32", "0.0.0.0"),
++                        ("0.0.0.0/0", "10.240.0.1"),
++                    ],
++                    "subnet-mask": "255.255.255.0",
++                }
++            ],
++            dhcp_udhcpc_discovery("/sbin/udhcpc", "eth9"),
++        )
++
++
+ class TestDHCPDiscoveryClean(CiTestCase):
+     with_logs = True
+ 
+@@ -372,7 +410,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
+             maybe_perform_dhcp_discovery()
+ 
+         self.assertIn(
+-            "Skip dhclient configuration: No dhclient command found.",
++            "Skip dhclient configuration: No dhclient or udhcpc command found.",
+             self.logs.getvalue(),
+         )
+ 
+-- 
+2.38.4
+
diff --git a/pkgs/tools/virtualization/cloud-init/default.nix b/pkgs/tools/virtualization/cloud-init/default.nix
index 97c80ec560c..53e6a3a612f 100644
--- a/pkgs/tools/virtualization/cloud-init/default.nix
+++ b/pkgs/tools/virtualization/cloud-init/default.nix
@@ -10,21 +10,23 @@
 , shadow
 , systemd
 , coreutils
+, gitUpdater
+, busybox
 }:
 
 python3.pkgs.buildPythonApplication rec {
   pname = "cloud-init";
-  version = "22.4";
+  version = "23.1.1";
   namePrefix = "";
 
   src = fetchFromGitHub {
     owner = "canonical";
     repo = "cloud-init";
     rev = "refs/tags/${version}";
-    hash = "sha256-MsT5t2da79Eb9FlTLPr2893JcF0ujNnToJTCQRT1QEo=";
+    hash = "sha256-w1UP7JIt/+6UlASB8kv2Lil+1sMTDIrADoYOT/WtaeE=";
   };
 
-  patches = [ ./0001-add-nixos-support.patch ];
+  patches = [ ./0001-add-nixos-support.patch ./0002-Add-Udhcpc-support.patch ];
 
   prePatch = ''
     substituteInPlace setup.py \
@@ -72,7 +74,7 @@ python3.pkgs.buildPythonApplication rec {
   ];
 
   makeWrapperArgs = [
-    "--prefix PATH : ${lib.makeBinPath [ dmidecode cloud-utils.guest ]}/bin"
+    "--prefix PATH : ${lib.makeBinPath [ dmidecode cloud-utils.guest busybox ]}/bin"
   ];
 
   disabledTests = [
@@ -82,6 +84,7 @@ python3.pkgs.buildPythonApplication rec {
     "test_path_env_gets_set_from_main"
     # tries to read from /etc/ca-certificates.conf while inside the sandbox
     "test_handler_ca_certs"
+    "TestRemoveDefaultCaCerts"
     # Doesn't work in the sandbox
     "TestEphemeralDhcpNoNetworkSetup"
     "TestHasURLConnectivity"
@@ -112,13 +115,16 @@ python3.pkgs.buildPythonApplication rec {
     "cloudinit"
   ];
 
-  passthru.tests = { inherit (nixosTests) cloud-init cloud-init-hostname; };
+  passthru = {
+    tests = { inherit (nixosTests) cloud-init cloud-init-hostname; };
+    updateScript = gitUpdater { ignoredVersions = ".ubuntu.*"; };
+  };
 
   meta = with lib; {
-    homepage = "https://cloudinit.readthedocs.org";
+    homepage = "https://github.com/canonical/cloud-init";
     description = "Provides configuration and customization of cloud instance";
     license = with licenses; [ asl20 gpl3Plus ];
-    maintainers = with maintainers; [ illustris ];
+    maintainers = with maintainers; [ illustris jfroche ];
     platforms = platforms.all;
   };
 }