diff --git a/SOURCES/ci-Add-native-NetworkManager-support-1224.patch b/SOURCES/ci-Add-native-NetworkManager-support-1224.patch
new file mode 100644
index 0000000..aad448a
--- /dev/null
+++ b/SOURCES/ci-Add-native-NetworkManager-support-1224.patch
@@ -0,0 +1,2300 @@
+From 0d93e53fd05c44b62e3456b7580c9de8135e6b5a Mon Sep 17 00:00:00 2001
+From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date: Mon, 2 May 2022 14:21:24 +0200
+Subject: [PATCH 1/4] Add native NetworkManager support (#1224)
+
+RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+RH-MergeRequest: 57: Add native NetworkManager support (#1224)
+RH-Commit: [1/2] 56b9ed40840a4930c421c2749e8aa385097bef93
+RH-Bugzilla: 2059872
+RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
+RH-Acked-by: Jon Maloy <jmaloy@redhat.com>
+RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
+
+commit feda344e6cf9d37b09bc13cf333a717d1654c26c
+Author: Lubomir Rintel <lkundrak@v3.sk>
+Date:   Fri Feb 25 23:33:20 2022 +0100
+
+    Add native NetworkManager support (#1224)
+
+    Fedora currently relies on sysconfig/ifcfg renderer. This is not too great,
+    because Fedora (also RHEL since version 8) dropped support for the legacy
+    network service that uses ifcfg files long ago.
+
+    In turn, Fedora ended up patching cloud-init downstream to utilize
+    NetworkManager's ifcfg compatibility mode [1]. This seems to have worked
+    for a while, nevertheless the NetworkManager's ifcfg backend is reaching
+    the end of its useful life too [2].
+
+    [1] https://src.fedoraproject.org/rpms/cloud-init/blob/rawhide/f/cloud-init-21.3-nm-controlled.patch
+    [2] https://fedoraproject.org/wiki/Changes/NoIfcfgFiles
+
+    Let's not mangle things downstream and make vanilla cloud-init work great
+    on Fedora instead.
+
+    This also means that the sysconfig compatibility with
+    Network Manager was removed.
+
+    Firstly, this relies upon the fact that you can get ifcfg support by adding
+    it to NetworkManager.conf. That is not guaranteed and certainly will not
+    be case in future.
+
+    Secondly, cloud-init always generates configuration with
+    NM_CONTROLLED=no, so the generated ifcfg files are no good for
+    NetworkManager. Fedora patches around this by just removing those lines
+    in their cloud-init package.
+
+Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+---
+ cloudinit/cmd/devel/net_convert.py     |   14 +-
+ cloudinit/net/activators.py            |   25 +-
+ cloudinit/net/network_manager.py       |  377 +++++++
+ cloudinit/net/renderers.py             |    3 +
+ cloudinit/net/sysconfig.py             |   37 +-
+ tests/unittests/test_net.py            | 1270 +++++++++++++++++++++---
+ tests/unittests/test_net_activators.py |   93 +-
+ 7 files changed, 1625 insertions(+), 194 deletions(-)
+ create mode 100644 cloudinit/net/network_manager.py
+
+diff --git a/cloudinit/cmd/devel/net_convert.py b/cloudinit/cmd/devel/net_convert.py
+index 18b1e7ff..647fe07b 100755
+--- a/cloudinit/cmd/devel/net_convert.py
++++ b/cloudinit/cmd/devel/net_convert.py
+@@ -7,7 +7,14 @@ import os
+ import sys
+ 
+ from cloudinit import distros, log, safeyaml
+-from cloudinit.net import eni, netplan, network_state, networkd, sysconfig
++from cloudinit.net import (
++    eni,
++    netplan,
++    network_manager,
++    network_state,
++    networkd,
++    sysconfig,
++)
+ from cloudinit.sources import DataSourceAzure as azure
+ from cloudinit.sources import DataSourceOVF as ovf
+ from cloudinit.sources.helpers import openstack
+@@ -74,7 +81,7 @@ def get_parser(parser=None):
+     parser.add_argument(
+         "-O",
+         "--output-kind",
+-        choices=["eni", "netplan", "networkd", "sysconfig"],
++        choices=["eni", "netplan", "networkd", "sysconfig", "network-manager"],
+         required=True,
+         help="The network config format to emit",
+     )
+@@ -148,6 +155,9 @@ def handle_args(name, args):
+     elif args.output_kind == "sysconfig":
+         r_cls = sysconfig.Renderer
+         config = distro.renderer_configs.get("sysconfig")
++    elif args.output_kind == "network-manager":
++        r_cls = network_manager.Renderer
++        config = distro.renderer_configs.get("network-manager")
+     else:
+         raise RuntimeError("Invalid output_kind")
+ 
+diff --git a/cloudinit/net/activators.py b/cloudinit/net/activators.py
+index e80c26df..edbc0c06 100644
+--- a/cloudinit/net/activators.py
++++ b/cloudinit/net/activators.py
+@@ -1,15 +1,14 @@
+ # This file is part of cloud-init. See LICENSE file for license information.
+ import logging
+-import os
+ from abc import ABC, abstractmethod
+ from typing import Iterable, List, Type
+ 
+ from cloudinit import subp, util
+ from cloudinit.net.eni import available as eni_available
+ from cloudinit.net.netplan import available as netplan_available
++from cloudinit.net.network_manager import available as nm_available
+ from cloudinit.net.network_state import NetworkState
+ from cloudinit.net.networkd import available as networkd_available
+-from cloudinit.net.sysconfig import NM_CFG_FILE
+ 
+ LOG = logging.getLogger(__name__)
+ 
+@@ -124,20 +123,24 @@ class IfUpDownActivator(NetworkActivator):
+ class NetworkManagerActivator(NetworkActivator):
+     @staticmethod
+     def available(target=None) -> bool:
+-        """Return true if network manager can be used on this system."""
+-        config_present = os.path.isfile(
+-            subp.target_path(target, path=NM_CFG_FILE)
+-        )
+-        nmcli_present = subp.which("nmcli", target=target)
+-        return config_present and bool(nmcli_present)
++        """Return true if NetworkManager can be used on this system."""
++        return nm_available(target=target)
+ 
+     @staticmethod
+     def bring_up_interface(device_name: str) -> bool:
+-        """Bring up interface using nmcli.
++        """Bring up connection using nmcli.
+ 
+         Return True is successful, otherwise return False
+         """
+-        cmd = ["nmcli", "connection", "up", "ifname", device_name]
++        from cloudinit.net.network_manager import conn_filename
++
++        filename = conn_filename(device_name)
++        cmd = ["nmcli", "connection", "load", filename]
++        if _alter_interface(cmd, device_name):
++            cmd = ["nmcli", "connection", "up", "filename", filename]
++        else:
++            _alter_interface(["nmcli", "connection", "reload"], device_name)
++            cmd = ["nmcli", "connection", "up", "ifname", device_name]
+         return _alter_interface(cmd, device_name)
+ 
+     @staticmethod
+@@ -146,7 +149,7 @@ class NetworkManagerActivator(NetworkActivator):
+ 
+         Return True is successful, otherwise return False
+         """
+-        cmd = ["nmcli", "connection", "down", device_name]
++        cmd = ["nmcli", "device", "disconnect", device_name]
+         return _alter_interface(cmd, device_name)
+ 
+ 
+diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py
+new file mode 100644
+index 00000000..79b0fe0b
+--- /dev/null
++++ b/cloudinit/net/network_manager.py
+@@ -0,0 +1,377 @@
++# Copyright 2022 Red Hat, Inc.
++#
++# Author: Lubomir Rintel <lkundrak@v3.sk>
++# Fixes and suggestions contributed by James Falcon, Neal Gompa,
++# Zbigniew Jędrzejewski-Szmek and Emanuele Giuseppe Esposito.
++#
++# This file is part of cloud-init. See LICENSE file for license information.
++
++import configparser
++import io
++import itertools
++import os
++import uuid
++
++from cloudinit import log as logging
++from cloudinit import subp, util
++
++from . import renderer
++from .network_state import is_ipv6_addr, subnet_is_ipv6
++
++NM_RUN_DIR = "/etc/NetworkManager"
++NM_LIB_DIR = "/usr/lib/NetworkManager"
++LOG = logging.getLogger(__name__)
++
++
++class NMConnection:
++    """Represents a NetworkManager connection profile."""
++
++    def __init__(self, con_id):
++        """
++        Initializes the connection with some very basic properties,
++        notably the UUID so that the connection can be referred to.
++        """
++
++        # Chosen by fair dice roll
++        CI_NM_UUID = uuid.UUID("a3924cb8-09e0-43e9-890b-77972a800108")
++
++        self.config = configparser.ConfigParser()
++        # Identity option name mapping, to achieve case sensitivity
++        self.config.optionxform = str
++
++        self.config["connection"] = {
++            "id": f"cloud-init {con_id}",
++            "uuid": str(uuid.uuid5(CI_NM_UUID, con_id)),
++        }
++
++        # This is not actually used anywhere, but may be useful in future
++        self.config["user"] = {
++            "org.freedesktop.NetworkManager.origin": "cloud-init"
++        }
++
++    def _set_default(self, section, option, value):
++        """
++        Sets a property unless it's already set, ensuring the section
++        exists.
++        """
++
++        if not self.config.has_section(section):
++            self.config[section] = {}
++        if not self.config.has_option(section, option):
++            self.config[section][option] = value
++
++    def _set_ip_method(self, family, subnet_type):
++        """
++        Ensures there's appropriate [ipv4]/[ipv6] for given family
++        appropriate for given configuration type
++        """
++
++        method_map = {
++            "static": "manual",
++            "dhcp6": "dhcp",
++            "ipv6_slaac": "auto",
++            "ipv6_dhcpv6-stateless": "auto",
++            "ipv6_dhcpv6-stateful": "auto",
++            "dhcp4": "auto",
++            "dhcp": "auto",
++        }
++
++        # Ensure we got an [ipvX] section
++        self._set_default(family, "method", "disabled")
++
++        try:
++            method = method_map[subnet_type]
++        except KeyError:
++            # What else can we do
++            method = "auto"
++            self.config[family]["may-fail"] = "true"
++
++        # Make sure we don't "downgrade" the method in case
++        # we got conflicting subnets (e.g. static along with dhcp)
++        if self.config[family]["method"] == "dhcp":
++            return
++        if self.config[family]["method"] == "auto" and method == "manual":
++            return
++
++        self.config[family]["method"] = method
++        self._set_default(family, "may-fail", "false")
++        if family == "ipv6":
++            self._set_default(family, "addr-gen-mode", "stable-privacy")
++
++    def _add_numbered(self, section, key_prefix, value):
++        """
++        Adds a numbered property, such as address<n> or route<n>, ensuring
++        the appropriate value gets used for <n>.
++        """
++
++        for index in itertools.count(1):
++            key = f"{key_prefix}{index}"
++            if not self.config.has_option(section, key):
++                self.config[section][key] = value
++                break
++
++    def _add_address(self, family, subnet):
++        """
++        Adds an ipv[46]address<n> property.
++        """
++
++        value = subnet["address"] + "/" + str(subnet["prefix"])
++        self._add_numbered(family, "address", value)
++
++    def _add_route(self, family, route):
++        """
++        Adds a ipv[46].route<n> property.
++        """
++
++        value = route["network"] + "/" + str(route["prefix"])
++        if "gateway" in route:
++            value = value + "," + route["gateway"]
++        self._add_numbered(family, "route", value)
++
++    def _add_nameserver(self, dns):
++        """
++        Extends the ipv[46].dns property with a name server.
++        """
++
++        # FIXME: the subnet contains IPv4 and IPv6 name server mixed
++        # together. We might be getting an IPv6 name server while
++        # we're dealing with an IPv4 subnet. Sort this out by figuring
++        # out the correct family and making sure a valid section exist.
++        family = "ipv6" if is_ipv6_addr(dns) else "ipv4"
++        self._set_default(family, "method", "disabled")
++
++        self._set_default(family, "dns", "")
++        self.config[family]["dns"] = self.config[family]["dns"] + dns + ";"
++
++    def _add_dns_search(self, family, dns_search):
++        """
++        Extends the ipv[46].dns-search property with a name server.
++        """
++
++        self._set_default(family, "dns-search", "")
++        self.config[family]["dns-search"] = (
++            self.config[family]["dns-search"] + ";".join(dns_search) + ";"
++        )
++
++    def con_uuid(self):
++        """
++        Returns the connection UUID
++        """
++        return self.config["connection"]["uuid"]
++
++    def valid(self):
++        """
++        Can this be serialized into a meaningful connection profile?
++        """
++        return self.config.has_option("connection", "type")
++
++    @staticmethod
++    def mac_addr(addr):
++        """
++        Sanitize a MAC address.
++        """
++        return addr.replace("-", ":").upper()
++
++    def render_interface(self, iface, renderer):
++        """
++        Integrate information from network state interface information
++        into the connection. Most of the work is done here.
++        """
++
++        # Initialize type & connectivity
++        _type_map = {
++            "physical": "ethernet",
++            "vlan": "vlan",
++            "bond": "bond",
++            "bridge": "bridge",
++            "infiniband": "infiniband",
++            "loopback": None,
++        }
++
++        if_type = _type_map[iface["type"]]
++        if if_type is None:
++            return
++        if "bond-master" in iface:
++            slave_type = "bond"
++        else:
++            slave_type = None
++
++        self.config["connection"]["type"] = if_type
++        if slave_type is not None:
++            self.config["connection"]["slave-type"] = slave_type
++            self.config["connection"]["master"] = renderer.con_ref(
++                iface[slave_type + "-master"]
++            )
++
++        # Add type specific-section
++        self.config[if_type] = {}
++
++        # These are the interface properties that map nicely
++        # to NetworkManager properties
++        _prop_map = {
++            "bond": {
++                "mode": "bond-mode",
++                "miimon": "bond_miimon",
++                "xmit_hash_policy": "bond-xmit-hash-policy",
++                "num_grat_arp": "bond-num-grat-arp",
++                "downdelay": "bond-downdelay",
++                "updelay": "bond-updelay",
++                "fail_over_mac": "bond-fail-over-mac",
++                "primary_reselect": "bond-primary-reselect",
++                "primary": "bond-primary",
++            },
++            "bridge": {
++                "stp": "bridge_stp",
++                "priority": "bridge_bridgeprio",
++            },
++            "vlan": {
++                "id": "vlan_id",
++            },
++            "ethernet": {},
++            "infiniband": {},
++        }
++
++        device_mtu = iface["mtu"]
++        ipv4_mtu = None
++
++        # Deal with Layer 3 configuration
++        for subnet in iface["subnets"]:
++            family = "ipv6" if subnet_is_ipv6(subnet) else "ipv4"
++
++            self._set_ip_method(family, subnet["type"])
++            if "address" in subnet:
++                self._add_address(family, subnet)
++            if "gateway" in subnet:
++                self.config[family]["gateway"] = subnet["gateway"]
++            for route in subnet["routes"]:
++                self._add_route(family, route)
++            if "dns_nameservers" in subnet:
++                for nameserver in subnet["dns_nameservers"]:
++                    self._add_nameserver(nameserver)
++            if "dns_search" in subnet:
++                self._add_dns_search(family, subnet["dns_search"])
++            if family == "ipv4" and "mtu" in subnet:
++                ipv4_mtu = subnet["mtu"]
++
++        if ipv4_mtu is None:
++            ipv4_mtu = device_mtu
++        if not ipv4_mtu == device_mtu:
++            LOG.warning(
++                "Network config: ignoring %s device-level mtu:%s"
++                " because ipv4 subnet-level mtu:%s provided.",
++                iface["name"],
++                device_mtu,
++                ipv4_mtu,
++            )
++
++        # Parse type-specific properties
++        for nm_prop, key in _prop_map[if_type].items():
++            if key not in iface:
++                continue
++            if iface[key] is None:
++                continue
++            if isinstance(iface[key], bool):
++                self.config[if_type][nm_prop] = (
++                    "true" if iface[key] else "false"
++                )
++            else:
++                self.config[if_type][nm_prop] = str(iface[key])
++
++        # These ones need special treatment
++        if if_type == "ethernet":
++            if iface["wakeonlan"] is True:
++                # NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC
++                self.config["ethernet"]["wake-on-lan"] = str(0x40)
++            if ipv4_mtu is not None:
++                self.config["ethernet"]["mtu"] = str(ipv4_mtu)
++            if iface["mac_address"] is not None:
++                self.config["ethernet"]["mac-address"] = self.mac_addr(
++                    iface["mac_address"]
++                )
++        if if_type == "vlan" and "vlan-raw-device" in iface:
++            self.config["vlan"]["parent"] = renderer.con_ref(
++                iface["vlan-raw-device"]
++            )
++        if if_type == "bridge":
++            # Bridge is ass-backwards compared to bond
++            for port in iface["bridge_ports"]:
++                port = renderer.get_conn(port)
++                port._set_default("connection", "slave-type", "bridge")
++                port._set_default("connection", "master", self.con_uuid())
++            if iface["mac_address"] is not None:
++                self.config["bridge"]["mac-address"] = self.mac_addr(
++                    iface["mac_address"]
++                )
++        if if_type == "infiniband" and ipv4_mtu is not None:
++            self.config["infiniband"]["transport-mode"] = "datagram"
++            self.config["infiniband"]["mtu"] = str(ipv4_mtu)
++            if iface["mac_address"] is not None:
++                self.config["infiniband"]["mac-address"] = self.mac_addr(
++                    iface["mac_address"]
++                )
++
++        # Finish up
++        if if_type == "bridge" or not self.config.has_option(
++            if_type, "mac-address"
++        ):
++            self.config["connection"]["interface-name"] = iface["name"]
++
++    def dump(self):
++        """
++        Stringify.
++        """
++
++        buf = io.StringIO()
++        self.config.write(buf, space_around_delimiters=False)
++        header = "# Generated by cloud-init. Changes will be lost.\n\n"
++        return header + buf.getvalue()
++
++
++class Renderer(renderer.Renderer):
++    """Renders network information in a NetworkManager keyfile format."""
++
++    def __init__(self, config=None):
++        self.connections = {}
++
++    def get_conn(self, con_id):
++        return self.connections[con_id]
++
++    def con_ref(self, con_id):
++        if con_id in self.connections:
++            return self.connections[con_id].con_uuid()
++        else:
++            # Well, what can we do...
++            return con_id
++
++    def render_network_state(self, network_state, templates=None, target=None):
++        # First pass makes sure there's NMConnections for all known
++        # interfaces that have UUIDs that can be linked to from related
++        # interfaces
++        for iface in network_state.iter_interfaces():
++            self.connections[iface["name"]] = NMConnection(iface["name"])
++
++        # Now render the actual interface configuration
++        for iface in network_state.iter_interfaces():
++            conn = self.connections[iface["name"]]
++            conn.render_interface(iface, self)
++
++        # And finally write the files
++        for con_id, conn in self.connections.items():
++            if not conn.valid():
++                continue
++            name = conn_filename(con_id, target)
++            util.write_file(name, conn.dump(), 0o600)
++
++
++def conn_filename(con_id, target=None):
++    target_con_dir = subp.target_path(target, NM_RUN_DIR)
++    con_file = f"cloud-init-{con_id}.nmconnection"
++    return f"{target_con_dir}/system-connections/{con_file}"
++
++
++def available(target=None):
++    target_nm_dir = subp.target_path(target, NM_LIB_DIR)
++    return os.path.exists(target_nm_dir)
++
++
++# vi: ts=4 expandtab
+diff --git a/cloudinit/net/renderers.py b/cloudinit/net/renderers.py
+index c755f04c..7edc34b5 100644
+--- a/cloudinit/net/renderers.py
++++ b/cloudinit/net/renderers.py
+@@ -8,6 +8,7 @@ from . import (
+     freebsd,
+     netbsd,
+     netplan,
++    network_manager,
+     networkd,
+     openbsd,
+     renderer,
+@@ -19,6 +20,7 @@ NAME_TO_RENDERER = {
+     "freebsd": freebsd,
+     "netbsd": netbsd,
+     "netplan": netplan,
++    "network-manager": network_manager,
+     "networkd": networkd,
+     "openbsd": openbsd,
+     "sysconfig": sysconfig,
+@@ -28,6 +30,7 @@ DEFAULT_PRIORITY = [
+     "eni",
+     "sysconfig",
+     "netplan",
++    "network-manager",
+     "freebsd",
+     "netbsd",
+     "openbsd",
+diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
+index 362e8d19..c3b0c795 100644
+--- a/cloudinit/net/sysconfig.py
++++ b/cloudinit/net/sysconfig.py
+@@ -5,8 +5,6 @@ import io
+ import os
+ import re
+ 
+-from configobj import ConfigObj
+-
+ from cloudinit import log as logging
+ from cloudinit import subp, util
+ from cloudinit.distros.parsers import networkmanager_conf, resolv_conf
+@@ -66,24 +64,6 @@ def _quote_value(value):
+         return value
+ 
+ 
+-def enable_ifcfg_rh(path):
+-    """Add ifcfg-rh to NetworkManager.cfg plugins if main section is present"""
+-    config = ConfigObj(path)
+-    if "main" in config:
+-        if "plugins" in config["main"]:
+-            if "ifcfg-rh" in config["main"]["plugins"]:
+-                return
+-        else:
+-            config["main"]["plugins"] = []
+-
+-        if isinstance(config["main"]["plugins"], list):
+-            config["main"]["plugins"].append("ifcfg-rh")
+-        else:
+-            config["main"]["plugins"] = [config["main"]["plugins"], "ifcfg-rh"]
+-        config.write()
+-        LOG.debug("Enabled ifcfg-rh NetworkManager plugins")
+-
+-
+ class ConfigMap(object):
+     """Sysconfig like dictionary object."""
+ 
+@@ -1031,8 +1011,6 @@ class Renderer(renderer.Renderer):
+             netrules_content = self._render_persistent_net(network_state)
+             netrules_path = subp.target_path(target, self.netrules_path)
+             util.write_file(netrules_path, netrules_content, file_mode)
+-        if available_nm(target=target):
+-            enable_ifcfg_rh(subp.target_path(target, path=NM_CFG_FILE))
+ 
+         sysconfig_path = subp.target_path(target, templates.get("control"))
+         # Distros configuring /etc/sysconfig/network as a file e.g. Centos
+@@ -1071,14 +1049,9 @@ def _supported_vlan_names(rdev, vid):
+ 
+ 
+ def available(target=None):
+-    sysconfig = available_sysconfig(target=target)
+-    nm = available_nm(target=target)
+-    return util.system_info()["variant"] in KNOWN_DISTROS and any(
+-        [nm, sysconfig]
+-    )
+-
++    if not util.system_info()["variant"] in KNOWN_DISTROS:
++        return False
+ 
+-def available_sysconfig(target=None):
+     expected = ["ifup", "ifdown"]
+     search = ["/sbin", "/usr/sbin"]
+     for p in expected:
+@@ -1095,10 +1068,4 @@ def available_sysconfig(target=None):
+     return False
+ 
+ 
+-def available_nm(target=None):
+-    if not os.path.isfile(subp.target_path(target, path=NM_CFG_FILE)):
+-        return False
+-    return True
+-
+-
+ # vi: ts=4 expandtab
+diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
+index 591241b3..ef21ad76 100644
+--- a/tests/unittests/test_net.py
++++ b/tests/unittests/test_net.py
+@@ -21,6 +21,7 @@ from cloudinit.net import (
+     interface_has_own_mac,
+     natural_sort_key,
+     netplan,
++    network_manager,
+     network_state,
+     networkd,
+     renderers,
+@@ -611,6 +612,37 @@ dns = none
+                 ),
+             ),
+         ],
++        "expected_network_manager": [
++            (
++                "".join(
++                    [
++                        "etc/NetworkManager/system-connections",
++                        "/cloud-init-eth0.nmconnection",
++                    ]
++                ),
++                """
++# Generated by cloud-init. Changes will be lost.
++
++[connection]
++id=cloud-init eth0
++uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
++type=ethernet
++
++[user]
++org.freedesktop.NetworkManager.origin=cloud-init
++
++[ethernet]
++mac-address=FA:16:3E:ED:9A:59
++
++[ipv4]
++method=manual
++may-fail=false
++address1=172.19.1.34/22
++route1=0.0.0.0/0,172.19.3.254
++
++""".lstrip(),
++            ),
++        ],
+     },
+     {
+         "in_data": {
+@@ -1073,6 +1105,50 @@ NETWORK_CONFIGS = {
+                 USERCTL=no"""
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-eth1.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth1
++                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=CF:D6:AF:48:E8:80
++
++                """
++            ),
++            "cloud-init-eth99.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth99
++                uuid=b1b88000-1f03-5360-8377-1a2205efffb4
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=C0:D6:9F:2C:E8:80
++
++                [ipv4]
++                method=auto
++                may-fail=false
++                address1=192.168.21.3/24
++                route1=0.0.0.0/0,65.61.151.37
++                dns=8.8.8.8;8.8.4.4;
++                dns-search=barley.maas;sach.maas;
++
++                """
++            ),
++        },
+         "yaml": textwrap.dedent(
+             """
+             version: 1
+@@ -1145,6 +1221,34 @@ NETWORK_CONFIGS = {
+                 STARTMODE=auto"""
+             )
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                [ipv6]
++                method=dhcp
++                may-fail=false
++                addr-gen-mode=stable-privacy
++
++                """
++            ),
++        },
+         "yaml": textwrap.dedent(
+             """\
+             version: 1
+@@ -1247,6 +1351,37 @@ NETWORK_CONFIGS = {
+                 """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mtu=9000
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.14.2/24
++
++                [ipv6]
++                method=manual
++                may-fail=false
++                addr-gen-mode=stable-privacy
++                address1=2001:1::1/64
++
++                """
++            ),
++        },
+     },
+     "v6_and_v4": {
+         "expected_sysconfig_opensuse": {
+@@ -1257,6 +1392,34 @@ NETWORK_CONFIGS = {
+                 STARTMODE=auto"""
+             )
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv6]
++                method=dhcp
++                may-fail=false
++                addr-gen-mode=stable-privacy
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++            ),
++        },
+         "yaml": textwrap.dedent(
+             """\
+             version: 1
+@@ -1330,6 +1493,30 @@ NETWORK_CONFIGS = {
+                 """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv6]
++                method=dhcp
++                may-fail=false
++                addr-gen-mode=stable-privacy
++
++                """
++            ),
++        },
+     },
+     "dhcpv6_accept_ra": {
+         "expected_eni": textwrap.dedent(
+@@ -1537,6 +1724,30 @@ NETWORK_CONFIGS = {
+             """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv6]
++                method=auto
++                may-fail=false
++                addr-gen-mode=stable-privacy
++
++                """
++            ),
++        },
+     },
+     "static6": {
+         "yaml": textwrap.dedent(
+@@ -1625,6 +1836,30 @@ NETWORK_CONFIGS = {
+             """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv6]
++                method=auto
++                may-fail=false
++                addr-gen-mode=stable-privacy
++
++                """
++            ),
++        },
+     },
+     "dhcpv6_stateful": {
+         "expected_eni": textwrap.dedent(
+@@ -1724,6 +1959,29 @@ NETWORK_CONFIGS = {
+             """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++            ),
++        },
+         "yaml_v2": textwrap.dedent(
+             """\
+             version: 2
+@@ -1777,6 +2035,30 @@ NETWORK_CONFIGS = {
+             """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-iface0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init iface0
++                uuid=8ddfba48-857c-5e86-ac09-1b43eae0bf70
++                type=ethernet
++                interface-name=iface0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                wake-on-lan=64
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++            ),
++        },
+         "yaml_v2": textwrap.dedent(
+             """\
+             version: 2
+@@ -2215,6 +2497,254 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
+                 USERCTL=no"""
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-eth3.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth3
++                uuid=b7e95dda-7746-5bf8-bf33-6e5f3c926790
++                type=ethernet
++                slave-type=bridge
++                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=66:BB:9F:2C:E8:80
++
++                """
++            ),
++            "cloud-init-eth5.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth5
++                uuid=5fda13c7-9942-5e90-a41b-1d043bd725dc
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=98:BB:9F:2C:E8:8A
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++            ),
++            "cloud-init-ib0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init ib0
++                uuid=11a1dda7-78b4-5529-beba-d9b5f549ad7b
++                type=infiniband
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [infiniband]
++                transport-mode=datagram
++                mtu=9000
++                mac-address=A0:00:02:20:FE:80:00:00:00:00:00:00:EC:0D:9A:03:00:15:E2:C1
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.200.7/24
++
++                """
++            ),
++            "cloud-init-bond0.200.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init bond0.200
++                uuid=88984a9c-ff22-5233-9267-86315e0acaa7
++                type=vlan
++                interface-name=bond0.200
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [vlan]
++                id=200
++                parent=54317911-f840-516b-a10d-82cb4c1f075c
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++            ),
++            "cloud-init-eth0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth0
++                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=C0:D6:9F:2C:E8:80
++
++                """
++            ),
++            "cloud-init-eth4.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth4
++                uuid=e27e4959-fb50-5580-b9a4-2073554627b9
++                type=ethernet
++                slave-type=bridge
++                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=98:BB:9F:2C:E8:80
++
++                """
++            ),
++            "cloud-init-eth1.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth1
++                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
++                type=ethernet
++                slave-type=bond
++                master=54317911-f840-516b-a10d-82cb4c1f075c
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=AA:D6:9F:2C:E8:80
++
++                """
++            ),
++            "cloud-init-br0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init br0
++                uuid=dee46ce4-af7a-5e7c-aa08-b25533ae9213
++                type=bridge
++                interface-name=br0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [bridge]
++                stp=false
++                priority=22
++                mac-address=BB:BB:BB:BB:BB:AA
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.14.2/24
++
++                [ipv6]
++                method=manual
++                may-fail=false
++                addr-gen-mode=stable-privacy
++                address1=2001:1::1/64
++                route1=::/0,2001:4800:78ff:1b::1
++
++                """
++            ),
++            "cloud-init-eth0.101.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth0.101
++                uuid=b5acec5e-db80-5935-8b02-0d5619fc42bf
++                type=vlan
++                interface-name=eth0.101
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [vlan]
++                id=101
++                parent=1dd9a779-d327-56e1-8454-c65e2556c12c
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.0.2/24
++                gateway=192.168.0.1
++                dns=192.168.0.10;10.23.23.134;
++                dns-search=barley.maas;sacchromyces.maas;brettanomyces.maas;
++                address2=192.168.2.10/24
++
++                """
++            ),
++            "cloud-init-bond0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init bond0
++                uuid=54317911-f840-516b-a10d-82cb4c1f075c
++                type=bond
++                interface-name=bond0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [bond]
++                mode=active-backup
++                miimon=100
++                xmit_hash_policy=layer3+4
++
++                [ipv6]
++                method=dhcp
++                may-fail=false
++                addr-gen-mode=stable-privacy
++
++                """
++            ),
++            "cloud-init-eth2.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth2
++                uuid=5559a242-3421-5fdd-896e-9cb8313d5804
++                type=ethernet
++                slave-type=bond
++                master=54317911-f840-516b-a10d-82cb4c1f075c
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=C0:BB:9F:2C:E8:80
++
++                """
++            ),
++        },
+         "yaml": textwrap.dedent(
+             """
+             version: 1
+@@ -2403,10 +2933,10 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
+                   - type: static
+                     address: 2001:1::1/92
+                     routes:
+-                        - gateway: 2001:67c:1562:1
++                        - gateway: 2001:67c:1562::1
+                           network: 2001:67c:1
+                           netmask: "ffff:ffff::"
+-                        - gateway: 3001:67c:1562:1
++                        - gateway: 3001:67c:15::1
+                           network: 3001:67c:1
+                           netmask: "ffff:ffff::"
+                           metric: 10000
+@@ -2451,10 +2981,10 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
+                      -   to: 10.1.3.0/24
+                          via: 192.168.0.3
+                      -   to: 2001:67c:1/32
+-                         via: 2001:67c:1562:1
++                         via: 2001:67c:1562::1
+                      -   metric: 10000
+                          to: 3001:67c:1/32
+-                         via: 3001:67c:1562:1
++                         via: 3001:67c:15::1
+         """
+         ),
+         "expected_eni": textwrap.dedent(
+@@ -2514,11 +3044,11 @@ iface bond0 inet static
+ # control-alias bond0
+ iface bond0 inet6 static
+     address 2001:1::1/92
+-    post-up route add -A inet6 2001:67c:1/32 gw 2001:67c:1562:1 || true
+-    pre-down route del -A inet6 2001:67c:1/32 gw 2001:67c:1562:1 || true
+-    post-up route add -A inet6 3001:67c:1/32 gw 3001:67c:1562:1 metric 10000 \
++    post-up route add -A inet6 2001:67c:1/32 gw 2001:67c:1562::1 || true
++    pre-down route del -A inet6 2001:67c:1/32 gw 2001:67c:1562::1 || true
++    post-up route add -A inet6 3001:67c:1/32 gw 3001:67c:15::1 metric 10000 \
+ || true
+-    pre-down route del -A inet6 3001:67c:1/32 gw 3001:67c:1562:1 metric 10000 \
++    pre-down route del -A inet6 3001:67c:1/32 gw 3001:67c:15::1 metric 10000 \
+ || true
+         """
+         ),
+@@ -2561,8 +3091,8 @@ iface bond0 inet6 static
+                 -   to: 2001:67c:1562:8007::1/64
+                     via: 2001:67c:1562:8007::aac:40b2
+                 -   metric: 10000
+-                    to: 3001:67c:1562:8007::1/64
+-                    via: 3001:67c:1562:8007::aac:40b2
++                    to: 3001:67c:15:8007::1/64
++                    via: 3001:67c:15:8007::aac:40b2
+             """
+         ),
+         "expected_netplan-v2": textwrap.dedent(
+@@ -2594,8 +3124,8 @@ iface bond0 inet6 static
+                      -   to: 2001:67c:1562:8007::1/64
+                          via: 2001:67c:1562:8007::aac:40b2
+                      -   metric: 10000
+-                         to: 3001:67c:1562:8007::1/64
+-                         via: 3001:67c:1562:8007::aac:40b2
++                         to: 3001:67c:15:8007::1/64
++                         via: 3001:67c:15:8007::aac:40b2
+              ethernets:
+                  eth0:
+                      match:
+@@ -2694,8 +3224,8 @@ iface bond0 inet6 static
+                 """\
+         # Created by cloud-init on instance boot automatically, do not edit.
+         #
+-        2001:67c:1/32 via 2001:67c:1562:1  dev bond0
+-        3001:67c:1/32 via 3001:67c:1562:1 metric 10000 dev bond0
++        2001:67c:1/32 via 2001:67c:1562::1  dev bond0
++        3001:67c:1/32 via 3001:67c:15::1 metric 10000 dev bond0
+             """
+             ),
+             "route-bond0": textwrap.dedent(
+@@ -2718,6 +3248,88 @@ iface bond0 inet6 static
+         """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-bond0s0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init bond0s0
++                uuid=09d0b5b9-67e7-5577-a1af-74d1cf17a71e
++                type=ethernet
++                slave-type=bond
++                master=54317911-f840-516b-a10d-82cb4c1f075c
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=AA:BB:CC:DD:E8:00
++
++                """
++            ),
++            "cloud-init-bond0s1.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init bond0s1
++                uuid=4d9aca96-b515-5630-ad83-d13daac7f9d0
++                type=ethernet
++                slave-type=bond
++                master=54317911-f840-516b-a10d-82cb4c1f075c
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=AA:BB:CC:DD:E8:01
++
++                """
++            ),
++            "cloud-init-bond0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init bond0
++                uuid=54317911-f840-516b-a10d-82cb4c1f075c
++                type=bond
++                interface-name=bond0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [bond]
++                mode=active-backup
++                miimon=100
++                xmit_hash_policy=layer3+4
++                num_grat_arp=5
++                downdelay=10
++                updelay=20
++                fail_over_mac=active
++                primary_reselect=always
++                primary=bond0s0
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.0.2/24
++                gateway=192.168.0.1
++                route1=10.1.3.0/24,192.168.0.3
++                address2=192.168.1.2/24
++
++                [ipv6]
++                method=manual
++                may-fail=false
++                addr-gen-mode=stable-privacy
++                address1=2001:1::1/92
++                route1=2001:67c:1/32,2001:67c:1562::1
++                route2=3001:67c:1/32,3001:67c:15::1
++
++                """
++            ),
++        },
+     },
+     "vlan": {
+         "yaml": textwrap.dedent(
+@@ -2801,6 +3413,58 @@ iface bond0 inet6 static
+                 VLAN=yes"""
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-en0.99.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init en0.99
++                uuid=f594e2ed-f107-51df-b225-1dc530a5356b
++                type=vlan
++                interface-name=en0.99
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [vlan]
++                id=99
++                parent=e0ca478b-8d84-52ab-8fae-628482c629b5
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.2.2/24
++                address2=192.168.1.2/24
++                gateway=192.168.1.1
++
++                [ipv6]
++                method=manual
++                may-fail=false
++                addr-gen-mode=stable-privacy
++                address1=2001:1::bbbb/96
++                route1=::/0,2001:1::1
++
++                """
++            ),
++            "cloud-init-en0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init en0
++                uuid=e0ca478b-8d84-52ab-8fae-628482c629b5
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=AA:BB:CC:DD:E8:00
++
++                """
++            ),
++        },
+     },
+     "bridge": {
+         "yaml": textwrap.dedent(
+@@ -2909,6 +3573,82 @@ iface bond0 inet6 static
+                 """
+             ),
+         },
++        "expected_network_manager": {
++            "cloud-init-br0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init br0
++                uuid=dee46ce4-af7a-5e7c-aa08-b25533ae9213
++                type=bridge
++                interface-name=br0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [bridge]
++                stp=false
++                priority=22
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.2.2/24
++
++                """
++            ),
++            "cloud-init-eth0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth0
++                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
++                type=ethernet
++                slave-type=bridge
++                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=52:54:00:12:34:00
++
++                [ipv6]
++                method=manual
++                may-fail=false
++                addr-gen-mode=stable-privacy
++                address1=2001:1::100/96
++
++                """
++            ),
++            "cloud-init-eth1.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth1
++                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
++                type=ethernet
++                slave-type=bridge
++                master=dee46ce4-af7a-5e7c-aa08-b25533ae9213
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=52:54:00:12:34:01
++
++                [ipv6]
++                method=manual
++                may-fail=false
++                addr-gen-mode=stable-privacy
++                address1=2001:1::101/96
++
++                """
++            ),
++        },
+     },
+     "manual": {
+         "yaml": textwrap.dedent(
+@@ -3037,28 +3777,95 @@ iface bond0 inet6 static
+                 """
+             ),
+         },
+-    },
+-}
++        "expected_network_manager": {
++            "cloud-init-eth0.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
+ 
++                [connection]
++                id=cloud-init eth0
++                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
++                type=ethernet
+ 
+-CONFIG_V1_EXPLICIT_LOOPBACK = {
+-    "version": 1,
+-    "config": [
+-        {
+-            "name": "eth0",
+-            "type": "physical",
+-            "subnets": [{"control": "auto", "type": "dhcp"}],
+-        },
+-        {
+-            "name": "lo",
+-            "type": "loopback",
+-            "subnets": [{"control": "auto", "type": "loopback"}],
+-        },
+-    ],
+-}
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
+ 
++                [ethernet]
++                mac-address=52:54:00:12:34:00
+ 
+-CONFIG_V1_SIMPLE_SUBNET = {
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=192.168.1.2/24
++
++                """
++            ),
++            "cloud-init-eth1.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth1
++                uuid=3c50eb47-7260-5a6d-801d-bd4f587d6b58
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mtu=1480
++                mac-address=52:54:00:12:34:AA
++
++                [ipv4]
++                method=auto
++                may-fail=true
++
++                """
++            ),
++            "cloud-init-eth2.nmconnection": textwrap.dedent(
++                """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth2
++                uuid=5559a242-3421-5fdd-896e-9cb8313d5804
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=52:54:00:12:34:FF
++
++                [ipv4]
++                method=auto
++                may-fail=true
++
++                """
++            ),
++        },
++    },
++}
++
++
++CONFIG_V1_EXPLICIT_LOOPBACK = {
++    "version": 1,
++    "config": [
++        {
++            "name": "eth0",
++            "type": "physical",
++            "subnets": [{"control": "auto", "type": "dhcp"}],
++        },
++        {
++            "name": "lo",
++            "type": "loopback",
++            "subnets": [{"control": "auto", "type": "loopback"}],
++        },
++    ],
++}
++
++
++CONFIG_V1_SIMPLE_SUBNET = {
+     "version": 1,
+     "config": [
+         {
+@@ -3497,7 +4304,6 @@ class TestRhelSysConfigRendering(CiTestCase):
+ 
+     with_logs = True
+ 
+-    nm_cfg_file = "/etc/NetworkManager/NetworkManager.conf"
+     scripts_dir = "/etc/sysconfig/network-scripts"
+     header = (
+         "# Created by cloud-init on instance boot automatically, "
+@@ -4072,78 +4878,6 @@ USERCTL=no
+         self._compare_files_to_expected(entry[self.expected_name], found)
+         self._assert_headers(found)
+ 
+-    def test_check_ifcfg_rh(self):
+-        """ifcfg-rh plugin is added NetworkManager.conf if conf present."""
+-        render_dir = self.tmp_dir()
+-        nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file)
+-        util.ensure_dir(os.path.dirname(nm_cfg))
+-
+-        # write a template nm.conf, note plugins is a list here
+-        with open(nm_cfg, "w") as fh:
+-            fh.write("# test_check_ifcfg_rh\n[main]\nplugins=foo,bar\n")
+-        self.assertTrue(os.path.exists(nm_cfg))
+-
+-        # render and read
+-        entry = NETWORK_CONFIGS["small"]
+-        found = self._render_and_read(
+-            network_config=yaml.load(entry["yaml"]), dir=render_dir
+-        )
+-        self._compare_files_to_expected(entry[self.expected_name], found)
+-        self._assert_headers(found)
+-
+-        # check ifcfg-rh is in the 'plugins' list
+-        config = sysconfig.ConfigObj(nm_cfg)
+-        self.assertIn("ifcfg-rh", config["main"]["plugins"])
+-
+-    def test_check_ifcfg_rh_plugins_string(self):
+-        """ifcfg-rh plugin is append when plugins is a string."""
+-        render_dir = self.tmp_path("render")
+-        os.makedirs(render_dir)
+-        nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file)
+-        util.ensure_dir(os.path.dirname(nm_cfg))
+-
+-        # write a template nm.conf, note plugins is a value here
+-        util.write_file(nm_cfg, "# test_check_ifcfg_rh\n[main]\nplugins=foo\n")
+-
+-        # render and read
+-        entry = NETWORK_CONFIGS["small"]
+-        found = self._render_and_read(
+-            network_config=yaml.load(entry["yaml"]), dir=render_dir
+-        )
+-        self._compare_files_to_expected(entry[self.expected_name], found)
+-        self._assert_headers(found)
+-
+-        # check raw content has plugin
+-        nm_file_content = util.load_file(nm_cfg)
+-        self.assertIn("ifcfg-rh", nm_file_content)
+-
+-        # check ifcfg-rh is in the 'plugins' list
+-        config = sysconfig.ConfigObj(nm_cfg)
+-        self.assertIn("ifcfg-rh", config["main"]["plugins"])
+-
+-    def test_check_ifcfg_rh_plugins_no_plugins(self):
+-        """enable_ifcfg_plugin creates plugins value if missing."""
+-        render_dir = self.tmp_path("render")
+-        os.makedirs(render_dir)
+-        nm_cfg = subp.target_path(render_dir, path=self.nm_cfg_file)
+-        util.ensure_dir(os.path.dirname(nm_cfg))
+-
+-        # write a template nm.conf, note plugins is missing
+-        util.write_file(nm_cfg, "# test_check_ifcfg_rh\n[main]\n")
+-        self.assertTrue(os.path.exists(nm_cfg))
+-
+-        # render and read
+-        entry = NETWORK_CONFIGS["small"]
+-        found = self._render_and_read(
+-            network_config=yaml.load(entry["yaml"]), dir=render_dir
+-        )
+-        self._compare_files_to_expected(entry[self.expected_name], found)
+-        self._assert_headers(found)
+-
+-        # check ifcfg-rh is in the 'plugins' list
+-        config = sysconfig.ConfigObj(nm_cfg)
+-        self.assertIn("ifcfg-rh", config["main"]["plugins"])
+-
+     def test_netplan_dhcp_false_disable_dhcp_in_state(self):
+         """netplan config with dhcp[46]: False should not add dhcp in state"""
+         net_config = yaml.load(NETPLAN_DHCP_FALSE)
+@@ -4699,6 +5433,281 @@ STARTMODE=auto
+         self._assert_headers(found)
+ 
+ 
++@mock.patch(
++    "cloudinit.net.is_openvswitch_internal_interface",
++    mock.Mock(return_value=False),
++)
++class TestNetworkManagerRendering(CiTestCase):
++
++    with_logs = True
++
++    scripts_dir = "/etc/NetworkManager/system-connections"
++
++    expected_name = "expected_network_manager"
++
++    def _get_renderer(self):
++        return network_manager.Renderer()
++
++    def _render_and_read(self, network_config=None, state=None, dir=None):
++        if dir is None:
++            dir = self.tmp_dir()
++
++        if network_config:
++            ns = network_state.parse_net_config_data(network_config)
++        elif state:
++            ns = state
++        else:
++            raise ValueError("Expected data or state, got neither")
++
++        renderer = self._get_renderer()
++        renderer.render_network_state(ns, target=dir)
++        return dir2dict(dir)
++
++    def _compare_files_to_expected(self, expected, found):
++        orig_maxdiff = self.maxDiff
++        expected_d = dict(
++            (os.path.join(self.scripts_dir, k), v) for k, v in expected.items()
++        )
++
++        try:
++            self.maxDiff = None
++            self.assertEqual(expected_d, found)
++        finally:
++            self.maxDiff = orig_maxdiff
++
++    @mock.patch("cloudinit.net.util.get_cmdline", return_value="root=myroot")
++    @mock.patch("cloudinit.net.sys_dev_path")
++    @mock.patch("cloudinit.net.read_sys_net")
++    @mock.patch("cloudinit.net.get_devicelist")
++    def test_default_generation(
++        self,
++        mock_get_devicelist,
++        mock_read_sys_net,
++        mock_sys_dev_path,
++        m_get_cmdline,
++    ):
++        tmp_dir = self.tmp_dir()
++        _setup_test(
++            tmp_dir, mock_get_devicelist, mock_read_sys_net, mock_sys_dev_path
++        )
++
++        network_cfg = net.generate_fallback_config()
++        ns = network_state.parse_net_config_data(
++            network_cfg, skip_broken=False
++        )
++
++        render_dir = os.path.join(tmp_dir, "render")
++        os.makedirs(render_dir)
++
++        renderer = self._get_renderer()
++        renderer.render_network_state(ns, target=render_dir)
++
++        found = dir2dict(render_dir)
++        self._compare_files_to_expected(
++            {
++                "cloud-init-eth1000.nmconnection": textwrap.dedent(
++                    """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth1000
++                uuid=8c517500-0c95-5308-9c8a-3092eebc44eb
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=07:1C:C6:75:A4:BE
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++                ),
++            },
++            found,
++        )
++
++    def test_openstack_rendering_samples(self):
++        for os_sample in OS_SAMPLES:
++            render_dir = self.tmp_dir()
++            ex_input = os_sample["in_data"]
++            ex_mac_addrs = os_sample["in_macs"]
++            network_cfg = openstack.convert_net_json(
++                ex_input, known_macs=ex_mac_addrs
++            )
++            ns = network_state.parse_net_config_data(
++                network_cfg, skip_broken=False
++            )
++            renderer = self._get_renderer()
++            # render a multiple times to simulate reboots
++            renderer.render_network_state(ns, target=render_dir)
++            renderer.render_network_state(ns, target=render_dir)
++            renderer.render_network_state(ns, target=render_dir)
++            for fn, expected_content in os_sample.get(self.expected_name, []):
++                with open(os.path.join(render_dir, fn)) as fh:
++                    self.assertEqual(expected_content, fh.read())
++
++    def test_network_config_v1_samples(self):
++        ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET)
++        render_dir = self.tmp_path("render")
++        os.makedirs(render_dir)
++        renderer = self._get_renderer()
++        renderer.render_network_state(ns, target=render_dir)
++        found = dir2dict(render_dir)
++        self._compare_files_to_expected(
++            {
++                "cloud-init-interface0.nmconnection": textwrap.dedent(
++                    """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init interface0
++                uuid=8b6862ed-dbd6-5830-93f7-a91451c13828
++                type=ethernet
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++                mac-address=52:54:00:12:34:00
++
++                [ipv4]
++                method=manual
++                may-fail=false
++                address1=10.0.2.15/24
++                gateway=10.0.2.2
++
++                """
++                ),
++            },
++            found,
++        )
++
++    def test_config_with_explicit_loopback(self):
++        render_dir = self.tmp_path("render")
++        os.makedirs(render_dir)
++        ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
++        renderer = self._get_renderer()
++        renderer.render_network_state(ns, target=render_dir)
++        found = dir2dict(render_dir)
++        self._compare_files_to_expected(
++            {
++                "cloud-init-eth0.nmconnection": textwrap.dedent(
++                    """\
++                # Generated by cloud-init. Changes will be lost.
++
++                [connection]
++                id=cloud-init eth0
++                uuid=1dd9a779-d327-56e1-8454-c65e2556c12c
++                type=ethernet
++                interface-name=eth0
++
++                [user]
++                org.freedesktop.NetworkManager.origin=cloud-init
++
++                [ethernet]
++
++                [ipv4]
++                method=auto
++                may-fail=false
++
++                """
++                ),
++            },
++            found,
++        )
++
++    def test_bond_config(self):
++        entry = NETWORK_CONFIGS["bond"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_vlan_config(self):
++        entry = NETWORK_CONFIGS["vlan"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_bridge_config(self):
++        entry = NETWORK_CONFIGS["bridge"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_manual_config(self):
++        entry = NETWORK_CONFIGS["manual"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_all_config(self):
++        entry = NETWORK_CONFIGS["all"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++        self.assertNotIn(
++            "WARNING: Network config: ignoring eth0.101 device-level mtu",
++            self.logs.getvalue(),
++        )
++
++    def test_small_config(self):
++        entry = NETWORK_CONFIGS["small"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_v4_and_v6_static_config(self):
++        entry = NETWORK_CONFIGS["v4_and_v6_static"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++        expected_msg = (
++            "WARNING: Network config: ignoring iface0 device-level mtu:8999"
++            " because ipv4 subnet-level mtu:9000 provided."
++        )
++        self.assertIn(expected_msg, self.logs.getvalue())
++
++    def test_dhcpv6_only_config(self):
++        entry = NETWORK_CONFIGS["dhcpv6_only"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_simple_render_ipv6_slaac(self):
++        entry = NETWORK_CONFIGS["ipv6_slaac"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_dhcpv6_stateless_config(self):
++        entry = NETWORK_CONFIGS["dhcpv6_stateless"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_wakeonlan_disabled_config_v2(self):
++        entry = NETWORK_CONFIGS["wakeonlan_disabled"]
++        found = self._render_and_read(
++            network_config=yaml.load(entry["yaml_v2"])
++        )
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_wakeonlan_enabled_config_v2(self):
++        entry = NETWORK_CONFIGS["wakeonlan_enabled"]
++        found = self._render_and_read(
++            network_config=yaml.load(entry["yaml_v2"])
++        )
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_render_v4_and_v6(self):
++        entry = NETWORK_CONFIGS["v4_and_v6"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++    def test_render_v6_and_v4(self):
++        entry = NETWORK_CONFIGS["v6_and_v4"]
++        found = self._render_and_read(network_config=yaml.load(entry["yaml"]))
++        self._compare_files_to_expected(entry[self.expected_name], found)
++
++
++@mock.patch(
++    "cloudinit.net.is_openvswitch_internal_interface",
++    mock.Mock(return_value=False),
++)
+ class TestEniNetRendering(CiTestCase):
+     @mock.patch("cloudinit.net.util.get_cmdline", return_value="root=myroot")
+     @mock.patch("cloudinit.net.sys_dev_path")
+@@ -6136,9 +7145,9 @@ class TestNetworkdRoundTrip(CiTestCase):
+ 
+ class TestRenderersSelect:
+     @pytest.mark.parametrize(
+-        "renderer_selected,netplan,eni,nm,scfg,sys,networkd",
++        "renderer_selected,netplan,eni,sys,network_manager,networkd",
+         (
+-            # -netplan -ifupdown -nm -scfg -sys raises error
++            # -netplan -ifupdown -sys -network-manager -networkd raises error
+             (
+                 net.RendererNotFoundError,
+                 False,
+@@ -6146,52 +7155,51 @@ class TestRenderersSelect:
+                 False,
+                 False,
+                 False,
+-                False,
+             ),
+-            # -netplan +ifupdown -nm -scfg -sys selects eni
+-            ("eni", False, True, False, False, False, False),
+-            # +netplan +ifupdown -nm -scfg -sys selects eni
+-            ("eni", True, True, False, False, False, False),
+-            # +netplan -ifupdown -nm -scfg -sys selects netplan
+-            ("netplan", True, False, False, False, False, False),
+-            # Ubuntu with Network-Manager installed
+-            # +netplan -ifupdown +nm -scfg -sys selects netplan
+-            ("netplan", True, False, True, False, False, False),
+-            # Centos/OpenSuse with Network-Manager installed selects sysconfig
+-            # -netplan -ifupdown +nm -scfg +sys selects netplan
+-            ("sysconfig", False, False, True, False, True, False),
+-            # -netplan -ifupdown -nm -scfg -sys +networkd selects networkd
+-            ("networkd", False, False, False, False, False, True),
++            # -netplan +ifupdown -sys -nm -networkd selects eni
++            ("eni", False, True, False, False, False),
++            # +netplan +ifupdown -sys -nm -networkd selects eni
++            ("eni", True, True, False, False, False),
++            # +netplan -ifupdown -sys -nm -networkd selects netplan
++            ("netplan", True, False, False, False, False),
++            # +netplan -ifupdown -sys -nm -networkd selects netplan
++            ("netplan", True, False, False, False, False),
++            # -netplan -ifupdown +sys -nm -networkd selects sysconfig
++            ("sysconfig", False, False, True, False, False),
++            # -netplan -ifupdown +sys +nm -networkd selects sysconfig
++            ("sysconfig", False, False, True, True, False),
++            # -netplan -ifupdown -sys +nm -networkd selects nm
++            ("network-manager", False, False, False, True, False),
++            # -netplan -ifupdown -sys +nm +networkd selects nm
++            ("network-manager", False, False, False, True, True),
++            # -netplan -ifupdown -sys -nm +networkd selects networkd
++            ("networkd", False, False, False, False, True),
+         ),
+     )
+     @mock.patch("cloudinit.net.renderers.networkd.available")
++    @mock.patch("cloudinit.net.renderers.network_manager.available")
+     @mock.patch("cloudinit.net.renderers.netplan.available")
+     @mock.patch("cloudinit.net.renderers.sysconfig.available")
+-    @mock.patch("cloudinit.net.renderers.sysconfig.available_sysconfig")
+-    @mock.patch("cloudinit.net.renderers.sysconfig.available_nm")
+     @mock.patch("cloudinit.net.renderers.eni.available")
+     def test_valid_renderer_from_defaults_depending_on_availability(
+         self,
+         m_eni_avail,
+-        m_nm_avail,
+-        m_scfg_avail,
+         m_sys_avail,
+         m_netplan_avail,
++        m_network_manager_avail,
+         m_networkd_avail,
+         renderer_selected,
+         netplan,
+         eni,
+-        nm,
+-        scfg,
+         sys,
++        network_manager,
+         networkd,
+     ):
+         """Assert proper renderer per DEFAULT_PRIORITY given availability."""
+         m_eni_avail.return_value = eni  # ifupdown pkg presence
+-        m_nm_avail.return_value = nm  # network-manager presence
+-        m_scfg_avail.return_value = scfg  # sysconfig presence
+         m_sys_avail.return_value = sys  # sysconfig/ifup/down presence
+         m_netplan_avail.return_value = netplan  # netplan presence
++        m_network_manager_avail.return_value = network_manager  # NM presence
+         m_networkd_avail.return_value = networkd  # networkd presence
+         if isinstance(renderer_selected, str):
+             (renderer_name, _rnd_class) = renderers.select(
+@@ -6249,7 +7257,7 @@ class TestNetRenderers(CiTestCase):
+             priority=["sysconfig", "eni"],
+         )
+ 
+-    @mock.patch("cloudinit.net.sysconfig.available_sysconfig")
++    @mock.patch("cloudinit.net.sysconfig.available")
+     @mock.patch("cloudinit.util.system_info")
+     def test_sysconfig_available_uses_variant_mapping(self, m_info, m_avail):
+         m_avail.return_value = True
+diff --git a/tests/unittests/test_net_activators.py b/tests/unittests/test_net_activators.py
+index 3c29e2f7..4525c49c 100644
+--- a/tests/unittests/test_net_activators.py
++++ b/tests/unittests/test_net_activators.py
+@@ -41,18 +41,20 @@ NETPLAN_CALL_LIST = [
+ 
+ @pytest.fixture
+ def available_mocks():
+-    mocks = namedtuple("Mocks", "m_which, m_file")
++    mocks = namedtuple("Mocks", "m_which, m_file, m_exists")
+     with patch("cloudinit.subp.which", return_value=True) as m_which:
+         with patch("os.path.isfile", return_value=True) as m_file:
+-            yield mocks(m_which, m_file)
++            with patch("os.path.exists", return_value=True) as m_exists:
++                yield mocks(m_which, m_file, m_exists)
+ 
+ 
+ @pytest.fixture
+ def unavailable_mocks():
+-    mocks = namedtuple("Mocks", "m_which, m_file")
++    mocks = namedtuple("Mocks", "m_which, m_file, m_exists")
+     with patch("cloudinit.subp.which", return_value=False) as m_which:
+         with patch("os.path.isfile", return_value=False) as m_file:
+-            yield mocks(m_which, m_file)
++            with patch("os.path.exists", return_value=False) as m_exists:
++                yield mocks(m_which, m_file, m_exists)
+ 
+ 
+ class TestSearchAndSelect:
+@@ -113,10 +115,6 @@ NETPLAN_AVAILABLE_CALLS = [
+     (("netplan",), {"search": ["/usr/sbin", "/sbin"], "target": None}),
+ ]
+ 
+-NETWORK_MANAGER_AVAILABLE_CALLS = [
+-    (("nmcli",), {"target": None}),
+-]
+-
+ NETWORKD_AVAILABLE_CALLS = [
+     (("ip",), {"search": ["/usr/sbin", "/bin"], "target": None}),
+     (("systemctl",), {"search": ["/usr/sbin", "/bin"], "target": None}),
+@@ -128,7 +126,6 @@ NETWORKD_AVAILABLE_CALLS = [
+     [
+         (IfUpDownActivator, IF_UP_DOWN_AVAILABLE_CALLS),
+         (NetplanActivator, NETPLAN_AVAILABLE_CALLS),
+-        (NetworkManagerActivator, NETWORK_MANAGER_AVAILABLE_CALLS),
+         (NetworkdActivator, NETWORKD_AVAILABLE_CALLS),
+     ],
+ )
+@@ -144,8 +141,72 @@ IF_UP_DOWN_BRING_UP_CALL_LIST = [
+ ]
+ 
+ NETWORK_MANAGER_BRING_UP_CALL_LIST = [
+-    ((["nmcli", "connection", "up", "ifname", "eth0"],), {}),
+-    ((["nmcli", "connection", "up", "ifname", "eth1"],), {}),
++    (
++        (
++            [
++                "nmcli",
++                "connection",
++                "load",
++                "".join(
++                    [
++                        "/etc/NetworkManager/system-connections",
++                        "/cloud-init-eth0.nmconnection",
++                    ]
++                ),
++            ],
++        ),
++        {},
++    ),
++    (
++        (
++            [
++                "nmcli",
++                "connection",
++                "up",
++                "filename",
++                "".join(
++                    [
++                        "/etc/NetworkManager/system-connections",
++                        "/cloud-init-eth0.nmconnection",
++                    ]
++                ),
++            ],
++        ),
++        {},
++    ),
++    (
++        (
++            [
++                "nmcli",
++                "connection",
++                "load",
++                "".join(
++                    [
++                        "/etc/NetworkManager/system-connections",
++                        "/cloud-init-eth1.nmconnection",
++                    ]
++                ),
++            ],
++        ),
++        {},
++    ),
++    (
++        (
++            [
++                "nmcli",
++                "connection",
++                "up",
++                "filename",
++                "".join(
++                    [
++                        "/etc/NetworkManager/system-connections",
++                        "/cloud-init-eth1.nmconnection",
++                    ]
++                ),
++            ],
++        ),
++        {},
++    ),
+ ]
+ 
+ NETWORKD_BRING_UP_CALL_LIST = [
+@@ -169,9 +230,11 @@ class TestActivatorsBringUp:
+     def test_bring_up_interface(
+         self, m_subp, activator, expected_call_list, available_mocks
+     ):
++        index = 0
+         activator.bring_up_interface("eth0")
+-        assert len(m_subp.call_args_list) == 1
+-        assert m_subp.call_args_list[0] == expected_call_list[0]
++        for call in m_subp.call_args_list:
++            assert call == expected_call_list[index]
++            index += 1
+ 
+     @patch("cloudinit.subp.subp", return_value=("", ""))
+     def test_bring_up_interfaces(
+@@ -208,8 +271,8 @@ IF_UP_DOWN_BRING_DOWN_CALL_LIST = [
+ ]
+ 
+ NETWORK_MANAGER_BRING_DOWN_CALL_LIST = [
+-    ((["nmcli", "connection", "down", "eth0"],), {}),
+-    ((["nmcli", "connection", "down", "eth1"],), {}),
++    ((["nmcli", "device", "disconnect", "eth0"],), {}),
++    ((["nmcli", "device", "disconnect", "eth1"],), {}),
+ ]
+ 
+ NETWORKD_BRING_DOWN_CALL_LIST = [
+-- 
+2.35.3
+
diff --git a/SOURCES/ci-Align-rhel-custom-files-with-upstream-1431.patch b/SOURCES/ci-Align-rhel-custom-files-with-upstream-1431.patch
new file mode 100644
index 0000000..7346183
--- /dev/null
+++ b/SOURCES/ci-Align-rhel-custom-files-with-upstream-1431.patch
@@ -0,0 +1,257 @@
+From 5c99ba05086b1ec83ce7e0c64edb4add4b47d923 Mon Sep 17 00:00:00 2001
+From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date: Thu, 19 May 2022 11:14:39 +0200
+Subject: [PATCH 3/4] Align rhel custom files with upstream (#1431)
+
+RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+RH-MergeRequest: 65: Align rhel custom files with upstream (#1431)
+RH-Commit: [1/2] 5d9067175688b1006472a477b0916b81c73d5e07
+RH-Bugzilla: 2082071
+RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
+RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
+RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
+
+commit 9624758f91b61f4711e8d7b5c83075b5d23e0c43
+Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date:   Wed May 18 15:18:04 2022 +0200
+
+    Align rhel custom files with upstream (#1431)
+
+    So far RHEL had its own custom .service and cloud.cfg files,
+    that diverged from upstream. We always replaced the generated files
+    with the ones we had.
+
+    This caused only confusion and made it harder to rebase and backport
+    patches targeting these files.
+    At the same time, we are going to delete our custom downstream-only files
+    and use the ones generated by .tmpl.
+
+    The mapping is:
+    config/cloud.cfg.tmpl -> rhel/cloud.cfg
+    systemd/* -> rhel/systemd/*
+
+    Such rhel-specific files are open and available in the Centos repo:
+    https://gitlab.com/redhat/centos-stream/src/cloud-init
+
+    With this commit, we are also introducing modules in cloud.cfg that
+    were not in the default rhel cfg file, even though they should already
+    have been there with previous rebases and releases.
+    Anyways such modules support rhel as distro, and
+    therefore should cause no harm.
+
+    Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+
+    RHBZ: 2082071
+
+Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+---
+ config/cloud.cfg.tmpl                   | 23 +++++++++++++++++++++++
+ systemd/cloud-config.service.tmpl       |  4 ++++
+ systemd/cloud-final.service.tmpl        | 13 +++++++++++++
+ systemd/cloud-init-local.service.tmpl   | 22 +++++++++++++++++++++-
+ systemd/cloud-init.service.tmpl         |  6 +++++-
+ tests/unittests/test_render_cloudcfg.py |  1 +
+ 6 files changed, 67 insertions(+), 2 deletions(-)
+
+diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
+index 86beee3c..f4d2fd14 100644
+--- a/config/cloud.cfg.tmpl
++++ b/config/cloud.cfg.tmpl
+@@ -34,7 +34,11 @@ disable_root: true
+ 
+ {% if variant in ["almalinux", "alpine", "amazon", "centos", "cloudlinux", "eurolinux",
+                   "fedora", "miraclelinux", "openEuler", "rhel", "rocky", "virtuozzo"] %}
++{% if variant == "rhel" %}
++mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service,_netdev', '0', '2']
++{% else %}
+ mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
++{% endif %}
+ {% if variant == "amazon" %}
+ resize_rootfs: noblock
+ {% endif %}
+@@ -66,6 +70,14 @@ network:
+   config: disabled
+ {% endif %}
+ 
++{% if variant == "rhel" %}
++# Default redhat settings:
++ssh_deletekeys:   true
++ssh_genkeytypes:  ['rsa', 'ecdsa', 'ed25519']
++syslog_fix_perms: ~
++disable_vmware_customization: false
++{% endif %}
++
+ # The modules that run in the 'init' stage
+ cloud_init_modules:
+  - migrator
+@@ -107,10 +119,15 @@ cloud_config_modules:
+ {% endif %}
+ {% if variant not in ["photon"] %}
+  - ssh-import-id
++{% if variant not in ["rhel"] %}
+  - keyboard
++{% endif %}
+  - locale
+ {% endif %}
+  - set-passwords
++{% if variant in ["rhel"] %}
++ - rh_subscription
++{% endif %}
+ {% if variant in ["rhel", "fedora", "photon"] %}
+ {% if variant not in ["photon"] %}
+  - spacewalk
+@@ -239,6 +256,10 @@ system_info:
+      name: ec2-user
+      lock_passwd: True
+      gecos: EC2 Default User
++{% elif variant == "rhel" %}
++     name: cloud-user
++     lock_passwd: true
++     gecos: Cloud User
+ {% else %}
+      name: {{ variant }}
+      lock_passwd: True
+@@ -254,6 +275,8 @@ system_info:
+      groups: [adm, sudo]
+ {% elif variant == "arch" %}
+      groups: [wheel, users]
++{% elif variant == "rhel" %}
++     groups: [adm, systemd-journal]
+ {% else %}
+      groups: [wheel, adm, systemd-journal]
+ {% endif %}
+diff --git a/systemd/cloud-config.service.tmpl b/systemd/cloud-config.service.tmpl
+index 9d928ca2..d5568a6e 100644
+--- a/systemd/cloud-config.service.tmpl
++++ b/systemd/cloud-config.service.tmpl
+@@ -4,6 +4,10 @@ Description=Apply the settings specified in cloud-config
+ After=network-online.target cloud-config.target
+ After=snapd.seeded.service
+ Wants=network-online.target cloud-config.target
++{% if variant == "rhel" %}
++ConditionPathExists=!/etc/cloud/cloud-init.disabled
++ConditionKernelCommandLine=!cloud-init=disabled
++{% endif %}
+ 
+ [Service]
+ Type=oneshot
+diff --git a/systemd/cloud-final.service.tmpl b/systemd/cloud-final.service.tmpl
+index 8207b18c..85f423ac 100644
+--- a/systemd/cloud-final.service.tmpl
++++ b/systemd/cloud-final.service.tmpl
+@@ -7,6 +7,10 @@ After=multi-user.target
+ Before=apt-daily.service
+ {% endif %}
+ Wants=network-online.target cloud-config.service
++{% if variant == "rhel" %}
++ConditionPathExists=!/etc/cloud/cloud-init.disabled
++ConditionKernelCommandLine=!cloud-init=disabled
++{% endif %}
+ 
+ 
+ [Service]
+@@ -15,7 +19,16 @@ ExecStart=/usr/bin/cloud-init modules --mode=final
+ RemainAfterExit=yes
+ TimeoutSec=0
+ KillMode=process
++{% if variant == "rhel" %}
++# Restart NetworkManager if it is present and running.
++ExecStartPost=/bin/sh -c 'u=NetworkManager.service; \
++ out=$(systemctl show --property=SubState $u) || exit; \
++ [ "$out" = "SubState=running" ] || exit 0; \
++ systemctl reload-or-try-restart $u'
++{% else %}
+ TasksMax=infinity
++{% endif %}
++
+ 
+ # Output needs to appear in instance console output
+ StandardOutput=journal+console
+diff --git a/systemd/cloud-init-local.service.tmpl b/systemd/cloud-init-local.service.tmpl
+index 7166f640..a6b82650 100644
+--- a/systemd/cloud-init-local.service.tmpl
++++ b/systemd/cloud-init-local.service.tmpl
+@@ -1,23 +1,43 @@
+ ## template:jinja
+ [Unit]
+ Description=Initial cloud-init job (pre-networking)
+-{% if variant in ["ubuntu", "unknown", "debian"] %}
++{% if variant in ["ubuntu", "unknown", "debian", "rhel" ] %}
+ DefaultDependencies=no
+ {% endif %}
+ Wants=network-pre.target
+ After=hv_kvp_daemon.service
+ After=systemd-remount-fs.service
++{% if variant == "rhel" %}
++Requires=dbus.socket
++After=dbus.socket
++{% endif %}
+ Before=NetworkManager.service
++{% if variant == "rhel" %}
++Before=network.service
++{% endif %}
+ Before=network-pre.target
+ Before=shutdown.target
++{% if variant == "rhel" %}
++Before=firewalld.target
++Conflicts=shutdown.target
++{% endif %}
+ {% if variant in ["ubuntu", "unknown", "debian"] %}
+ Before=sysinit.target
+ Conflicts=shutdown.target
+ {% endif %}
+ RequiresMountsFor=/var/lib/cloud
++{% if variant == "rhel" %}
++ConditionPathExists=!/etc/cloud/cloud-init.disabled
++ConditionKernelCommandLine=!cloud-init=disabled
++{% endif %}
+ 
+ [Service]
+ Type=oneshot
++{% if variant == "rhel" %}
++ExecStartPre=/bin/mkdir -p /run/cloud-init
++ExecStartPre=/sbin/restorecon /run/cloud-init
++ExecStartPre=/usr/bin/touch /run/cloud-init/enabled
++{% endif %}
+ ExecStart=/usr/bin/cloud-init init --local
+ ExecStart=/bin/touch /run/cloud-init/network-config-ready
+ RemainAfterExit=yes
+diff --git a/systemd/cloud-init.service.tmpl b/systemd/cloud-init.service.tmpl
+index e71e5679..c170aef7 100644
+--- a/systemd/cloud-init.service.tmpl
++++ b/systemd/cloud-init.service.tmpl
+@@ -1,7 +1,7 @@
+ ## template:jinja
+ [Unit]
+ Description=Initial cloud-init job (metadata service crawler)
+-{% if variant not in ["photon"] %}
++{% if variant not in ["photon", "rhel"] %}
+ DefaultDependencies=no
+ {% endif %}
+ Wants=cloud-init-local.service
+@@ -36,6 +36,10 @@ Before=shutdown.target
+ Conflicts=shutdown.target
+ {% endif %}
+ Before=systemd-user-sessions.service
++{% if variant == "rhel" %}
++ConditionPathExists=!/etc/cloud/cloud-init.disabled
++ConditionKernelCommandLine=!cloud-init=disabled
++{% endif %}
+ 
+ [Service]
+ Type=oneshot
+diff --git a/tests/unittests/test_render_cloudcfg.py b/tests/unittests/test_render_cloudcfg.py
+index 30fbd1a4..9f95d448 100644
+--- a/tests/unittests/test_render_cloudcfg.py
++++ b/tests/unittests/test_render_cloudcfg.py
+@@ -68,6 +68,7 @@ class TestRenderCloudCfg:
+         default_user_exceptions = {
+             "amazon": "ec2-user",
+             "debian": "ubuntu",
++            "rhel": "cloud-user",
+             "unknown": "ubuntu",
+         }
+         default_user = system_cfg["system_info"]["default_user"]["name"]
+-- 
+2.35.3
+
diff --git a/SOURCES/ci-Remove-rhel-specific-files.patch b/SOURCES/ci-Remove-rhel-specific-files.patch
new file mode 100644
index 0000000..6765543
--- /dev/null
+++ b/SOURCES/ci-Remove-rhel-specific-files.patch
@@ -0,0 +1,373 @@
+From d43f0d93386f123892451d923c2b3c6fe7130c39 Mon Sep 17 00:00:00 2001
+From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date: Thu, 19 May 2022 11:38:22 +0200
+Subject: [PATCH 4/4] Remove rhel specific files
+
+RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+RH-MergeRequest: 65: Align rhel custom files with upstream (#1431)
+RH-Commit: [2/2] 5e31f0bcb500682e7746ccbd2e628c2ef339d6c6
+RH-Bugzilla: 2082071
+RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
+RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
+RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
+
+Remove all files in rhel/ directory and related commands that copy
+and replace them with the generated ones.
+
+Also adjust setup.py, align it with upstream:
+- by default, after rhel 8.3 ds-identify is in /usr/libexec, so no need to move it manually
+- bash-completions work also in /usr/share, as upstream
+- udev also works in /lib/udev
+
+Also remove rhel/README since it is outdated (chef is used in cloud.cfg) and cloud-init-tmpfiles.conf,
+as it exists also in .distro.
+
+X-downstream-only: yes
+
+Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+---
+ redhat/cloud-init.spec.template       | 21 ++------
+ rhel/README.rhel                      |  5 --
+ rhel/cloud-init-tmpfiles.conf         |  1 -
+ rhel/cloud.cfg                        | 69 ---------------------------
+ rhel/systemd/cloud-config.service     | 18 -------
+ rhel/systemd/cloud-config.target      | 11 -----
+ rhel/systemd/cloud-final.service      | 24 ----------
+ rhel/systemd/cloud-init-local.service | 31 ------------
+ rhel/systemd/cloud-init.service       | 26 ----------
+ rhel/systemd/cloud-init.target        |  7 ---
+ setup.py                              | 28 ++++++++++-
+ 11 files changed, 31 insertions(+), 210 deletions(-)
+ delete mode 100644 rhel/README.rhel
+ delete mode 100644 rhel/cloud-init-tmpfiles.conf
+ delete mode 100644 rhel/cloud.cfg
+ delete mode 100644 rhel/systemd/cloud-config.service
+ delete mode 100644 rhel/systemd/cloud-config.target
+ delete mode 100644 rhel/systemd/cloud-final.service
+ delete mode 100644 rhel/systemd/cloud-init-local.service
+ delete mode 100644 rhel/systemd/cloud-init.service
+ delete mode 100644 rhel/systemd/cloud-init.target
+
+ 
+diff --git a/rhel/README.rhel b/rhel/README.rhel
+deleted file mode 100644
+index aa29630d..00000000
+--- a/rhel/README.rhel
++++ /dev/null
+@@ -1,5 +0,0 @@
+-The following cloud-init modules are currently unsupported on this OS:
+- - apt_update_upgrade ('apt_update', 'apt_upgrade', 'apt_mirror', 'apt_preserve_sources_list', 'apt_old_mirror', 'apt_sources', 'debconf_selections', 'packages' options)
+- - byobu ('byobu_by_default' option)
+- - chef
+- - grub_dpkg
+diff --git a/rhel/cloud-init-tmpfiles.conf b/rhel/cloud-init-tmpfiles.conf
+deleted file mode 100644
+index 0c6d2a3b..00000000
+--- a/rhel/cloud-init-tmpfiles.conf
++++ /dev/null
+@@ -1 +0,0 @@
+-d /run/cloud-init 0700 root root - -
+diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg
+deleted file mode 100644
+index cbee197a..00000000
+--- a/rhel/cloud.cfg
++++ /dev/null
+@@ -1,69 +0,0 @@
+-users:
+- - default
+-
+-disable_root: 1
+-ssh_pwauth:   0
+-
+-mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2']
+-resize_rootfs_tmp: /dev
+-ssh_deletekeys:   1
+-ssh_genkeytypes:  ['rsa', 'ecdsa', 'ed25519']
+-syslog_fix_perms: ~
+-disable_vmware_customization: false
+-
+-cloud_init_modules:
+- - disk_setup
+- - migrator
+- - bootcmd
+- - write-files
+- - growpart
+- - resizefs
+- - set_hostname
+- - update_hostname
+- - update_etc_hosts
+- - rsyslog
+- - users-groups
+- - ssh
+-
+-cloud_config_modules:
+- - mounts
+- - locale
+- - set-passwords
+- - rh_subscription
+- - yum-add-repo
+- - package-update-upgrade-install
+- - timezone
+- - puppet
+- - chef
+- - salt-minion
+- - mcollective
+- - disable-ec2-metadata
+- - runcmd
+-
+-cloud_final_modules:
+- - rightscale_userdata
+- - scripts-per-once
+- - scripts-per-boot
+- - scripts-per-instance
+- - scripts-user
+- - ssh-authkey-fingerprints
+- - keys-to-console
+- - phone-home
+- - final-message
+- - power-state-change
+-
+-system_info:
+-  default_user:
+-    name: cloud-user
+-    lock_passwd: true
+-    gecos: Cloud User
+-    groups: [adm, systemd-journal]
+-    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
+-    shell: /bin/bash
+-  distro: rhel
+-  paths:
+-    cloud_dir: /var/lib/cloud
+-    templates_dir: /etc/cloud/templates
+-  ssh_svcname: sshd
+-
+-# vim:syntax=yaml
+diff --git a/rhel/systemd/cloud-config.service b/rhel/systemd/cloud-config.service
+deleted file mode 100644
+index f3dcd4be..00000000
+--- a/rhel/systemd/cloud-config.service
++++ /dev/null
+@@ -1,18 +0,0 @@
+-[Unit]
+-Description=Apply the settings specified in cloud-config
+-After=network-online.target cloud-config.target
+-Wants=network-online.target cloud-config.target
+-ConditionPathExists=!/etc/cloud/cloud-init.disabled
+-ConditionKernelCommandLine=!cloud-init=disabled
+-
+-[Service]
+-Type=oneshot
+-ExecStart=/usr/bin/cloud-init modules --mode=config
+-RemainAfterExit=yes
+-TimeoutSec=0
+-
+-# Output needs to appear in instance console output
+-StandardOutput=journal+console
+-
+-[Install]
+-WantedBy=cloud-init.target
+diff --git a/rhel/systemd/cloud-config.target b/rhel/systemd/cloud-config.target
+deleted file mode 100644
+index ae9b7d02..00000000
+--- a/rhel/systemd/cloud-config.target
++++ /dev/null
+@@ -1,11 +0,0 @@
+-# cloud-init normally emits a "cloud-config" upstart event to inform third
+-# parties that cloud-config is available, which does us no good when we're
+-# using systemd.  cloud-config.target serves as this synchronization point
+-# instead.  Services that would "start on cloud-config" with upstart can
+-# instead use "After=cloud-config.target" and "Wants=cloud-config.target"
+-# as appropriate.
+-
+-[Unit]
+-Description=Cloud-config availability
+-Wants=cloud-init-local.service cloud-init.service
+-After=cloud-init-local.service cloud-init.service
+diff --git a/rhel/systemd/cloud-final.service b/rhel/systemd/cloud-final.service
+deleted file mode 100644
+index e281c0cf..00000000
+--- a/rhel/systemd/cloud-final.service
++++ /dev/null
+@@ -1,24 +0,0 @@
+-[Unit]
+-Description=Execute cloud user/final scripts
+-After=network-online.target cloud-config.service rc-local.service
+-Wants=network-online.target cloud-config.service
+-ConditionPathExists=!/etc/cloud/cloud-init.disabled
+-ConditionKernelCommandLine=!cloud-init=disabled
+-
+-[Service]
+-Type=oneshot
+-ExecStart=/usr/bin/cloud-init modules --mode=final
+-RemainAfterExit=yes
+-TimeoutSec=0
+-KillMode=process
+-# Restart NetworkManager if it is present and running.
+-ExecStartPost=/bin/sh -c 'u=NetworkManager.service; \
+- out=$(systemctl show --property=SubState $u) || exit; \
+- [ "$out" = "SubState=running" ] || exit 0; \
+- systemctl reload-or-try-restart $u'
+-
+-# Output needs to appear in instance console output
+-StandardOutput=journal+console
+-
+-[Install]
+-WantedBy=cloud-init.target
+diff --git a/rhel/systemd/cloud-init-local.service b/rhel/systemd/cloud-init-local.service
+deleted file mode 100644
+index 8f9f6c9f..00000000
+--- a/rhel/systemd/cloud-init-local.service
++++ /dev/null
+@@ -1,31 +0,0 @@
+-[Unit]
+-Description=Initial cloud-init job (pre-networking)
+-DefaultDependencies=no
+-Wants=network-pre.target
+-After=systemd-remount-fs.service
+-Requires=dbus.socket
+-After=dbus.socket
+-Before=NetworkManager.service network.service
+-Before=network-pre.target
+-Before=shutdown.target
+-Before=firewalld.target
+-Conflicts=shutdown.target
+-RequiresMountsFor=/var/lib/cloud
+-ConditionPathExists=!/etc/cloud/cloud-init.disabled
+-ConditionKernelCommandLine=!cloud-init=disabled
+-
+-[Service]
+-Type=oneshot
+-ExecStartPre=/bin/mkdir -p /run/cloud-init
+-ExecStartPre=/sbin/restorecon /run/cloud-init
+-ExecStartPre=/usr/bin/touch /run/cloud-init/enabled
+-ExecStart=/usr/bin/cloud-init init --local
+-ExecStart=/bin/touch /run/cloud-init/network-config-ready
+-RemainAfterExit=yes
+-TimeoutSec=0
+-
+-# Output needs to appear in instance console output
+-StandardOutput=journal+console
+-
+-[Install]
+-WantedBy=cloud-init.target
+diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service
+deleted file mode 100644
+index 0b3d796d..00000000
+--- a/rhel/systemd/cloud-init.service
++++ /dev/null
+@@ -1,26 +0,0 @@
+-[Unit]
+-Description=Initial cloud-init job (metadata service crawler)
+-Wants=cloud-init-local.service
+-Wants=sshd-keygen.service
+-Wants=sshd.service
+-After=cloud-init-local.service
+-After=NetworkManager.service network.service
+-After=NetworkManager-wait-online.service
+-Before=network-online.target
+-Before=sshd-keygen.service
+-Before=sshd.service
+-Before=systemd-user-sessions.service
+-ConditionPathExists=!/etc/cloud/cloud-init.disabled
+-ConditionKernelCommandLine=!cloud-init=disabled
+-
+-[Service]
+-Type=oneshot
+-ExecStart=/usr/bin/cloud-init init
+-RemainAfterExit=yes
+-TimeoutSec=0
+-
+-# Output needs to appear in instance console output
+-StandardOutput=journal+console
+-
+-[Install]
+-WantedBy=cloud-init.target
+diff --git a/rhel/systemd/cloud-init.target b/rhel/systemd/cloud-init.target
+deleted file mode 100644
+index 083c3b6f..00000000
+--- a/rhel/systemd/cloud-init.target
++++ /dev/null
+@@ -1,7 +0,0 @@
+-# cloud-init target is enabled by cloud-init-generator
+-# To disable it you can either:
+-#  a.) boot with kernel cmdline of 'cloud-init=disabled'
+-#  b.) touch a file /etc/cloud/cloud-init.disabled
+-[Unit]
+-Description=Cloud-init target
+-After=multi-user.target
+diff --git a/setup.py b/setup.py
+index 3c377eaa..a9132d2c 100755
+--- a/setup.py
++++ b/setup.py
+@@ -139,6 +139,21 @@ INITSYS_FILES = {
+     "sysvinit_deb": [f for f in glob("sysvinit/debian/*") if is_f(f)],
+     "sysvinit_openrc": [f for f in glob("sysvinit/gentoo/*") if is_f(f)],
+     "sysvinit_suse": [f for f in glob("sysvinit/suse/*") if is_f(f)],
++    "systemd": [
++        render_tmpl(f)
++        for f in (
++            glob("systemd/*.tmpl")
++            + glob("systemd/*.service")
++            + glob("systemd/*.socket")
++            + glob("systemd/*.target")
++        )
++        if (is_f(f) and not is_generator(f))
++    ],
++    "systemd.generators": [
++        render_tmpl(f, mode=0o755)
++        for f in glob("systemd/*")
++        if is_f(f) and is_generator(f)
++    ],
+     "upstart": [f for f in glob("upstart/*") if is_f(f)],
+ }
+ INITSYS_ROOTS = {
+@@ -148,6 +163,10 @@ INITSYS_ROOTS = {
+     "sysvinit_deb": "etc/init.d",
+     "sysvinit_openrc": "etc/init.d",
+     "sysvinit_suse": "etc/init.d",
++    "systemd": pkg_config_read("systemd", "systemdsystemunitdir"),
++    "systemd.generators": pkg_config_read(
++        "systemd", "systemdsystemgeneratordir"
++    ),
+     "upstart": "etc/init/",
+ }
+ INITSYS_TYPES = sorted([f.partition(".")[0] for f in INITSYS_ROOTS.keys()])
+@@ -262,13 +281,15 @@ data_files = [
+     (
+         USR_LIB_EXEC + "/cloud-init",
+         [
++            "tools/ds-identify",
+             "tools/hook-hotplug",
+             "tools/uncloud-init",
+             "tools/write-ssh-key-fingerprints",
+         ],
+     ),
+     (
+-        ETC + "/bash_completion.d", ["bash_completion/cloud-init"],
++        USR + "/share/bash-completion/completions",
++        ["bash_completion/cloud-init"],
+     ),
+     (USR + "/share/doc/cloud-init", [f for f in glob("doc/*") if is_f(f)]),
+     (
+@@ -287,7 +308,8 @@ if not platform.system().endswith("BSD"):
+                 ETC + "/NetworkManager/dispatcher.d/",
+                 ["tools/hook-network-manager"],
+             ),
+-            ("/usr/lib/udev/rules.d", [f for f in glob("udev/*.rules")]),
++            (ETC + "/dhcp/dhclient-exit-hooks.d/", ["tools/hook-dhclient"]),
++            (LIB + "/udev/rules.d", [f for f in glob("udev/*.rules")]),
+             (
+                 ETC + "/systemd/system/sshd-keygen@.service.d/",
+                 ["systemd/disable-sshd-keygen-if-cloud-init-active.conf"],
+@@ -317,6 +339,8 @@ setuptools.setup(
+     scripts=["tools/cloud-init-per"],
+     license="Dual-licensed under GPLv3 or Apache 2.0",
+     data_files=data_files,
++    install_requires=requirements,
++    cmdclass=cmdclass,
+     entry_points={
+         "console_scripts": [
+             "cloud-init = cloudinit.cmd.main:main",
+-- 
+2.35.3
+
diff --git a/SOURCES/ci-Support-EC2-tags-in-instance-metadata-1309.patch b/SOURCES/ci-Support-EC2-tags-in-instance-metadata-1309.patch
new file mode 100644
index 0000000..6e8e0fb
--- /dev/null
+++ b/SOURCES/ci-Support-EC2-tags-in-instance-metadata-1309.patch
@@ -0,0 +1,164 @@
+From fbec3008305845072a787f46008bbb82d89dec53 Mon Sep 17 00:00:00 2001
+From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date: Mon, 30 May 2022 16:46:41 +0200
+Subject: [PATCH] Support EC2 tags in instance metadata (#1309)
+
+RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+RH-MergeRequest: 70: Support EC2 tags in instance metadata (#1309)
+RH-Commit: [1/1] 2497547016173a4c6e7d3c900f80de390d445c44
+RH-Bugzilla: 2082686
+RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
+RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
+
+commit 40c52ce1f4049449b04f93226721f63af874c5c7
+Author: Eduardo Dobay <edudobay@users.noreply.github.com>
+Date:   Wed Apr 6 01:28:01 2022 -0300
+
+    Support EC2 tags in instance metadata (#1309)
+
+    Add support for newer EC2 metadata versions (up to 2021-03-23), so that
+    tags can be retrieved from the `ds.meta_data.tags` field, as well as
+    with any new fields that might have been added since the 2018-09-24
+    version.
+
+Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+---
+ cloudinit/sources/DataSourceEc2.py  |  5 +++--
+ doc/rtd/topics/datasources/ec2.rst  | 28 ++++++++++++++++++++++------
+ tests/unittests/sources/test_ec2.py | 26 +++++++++++++++++++++++++-
+ tools/.github-cla-signers           |  1 +
+ 4 files changed, 51 insertions(+), 9 deletions(-)
+
+diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
+index 03b3870c..a030b498 100644
+--- a/cloudinit/sources/DataSourceEc2.py
++++ b/cloudinit/sources/DataSourceEc2.py
+@@ -61,8 +61,9 @@ class DataSourceEc2(sources.DataSource):
+     min_metadata_version = "2009-04-04"
+ 
+     # Priority ordered list of additional metadata versions which will be tried
+-    # for extended metadata content. IPv6 support comes in 2016-09-02
+-    extended_metadata_versions = ["2018-09-24", "2016-09-02"]
++    # for extended metadata content. IPv6 support comes in 2016-09-02.
++    # Tags support comes in 2021-03-23.
++    extended_metadata_versions = ["2021-03-23", "2018-09-24", "2016-09-02"]
+ 
+     # Setup read_url parameters per get_url_params.
+     url_max_wait = 120
+diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst
+index 94e4158d..77232269 100644
+--- a/doc/rtd/topics/datasources/ec2.rst
++++ b/doc/rtd/topics/datasources/ec2.rst
+@@ -38,11 +38,26 @@ Userdata is accessible via the following URL:
+     GET http://169.254.169.254/2009-04-04/user-data
+     1234,fred,reboot,true | 4512,jimbo, | 173,,,
+ 
+-Note that there are multiple versions of this data provided, cloud-init
+-by default uses **2009-04-04** but newer versions can be supported with
+-relative ease (newer versions have more data exposed, while maintaining
+-backward compatibility with the previous versions).
+-Version **2016-09-02** is required for secondary IP address support.
++Note that there are multiple EC2 Metadata versions of this data provided
++to instances. cloud-init will attempt to use the most recent API version it
++supports in order to get latest API features and instance-data. If a given
++API version is not exposed to the instance, those API features will be
++unavailable to the instance.
++
++
+++----------------+----------------------------------------------------------+
+++ EC2 version    | supported instance-data/feature                          |
+++================+==========================================================+
+++ **2021-03-23** | Required for Instance tag support. This feature must be  |
++|                | enabled individually on each instance.  See the          |
++|                | `EC2 tags user guide`_.                                  |
+++----------------+----------------------------------------------------------+
++| **2016-09-02** | Required for secondary IP address support.               |
+++----------------+----------------------------------------------------------+
++| **2009-04-04** | Minimum supports EC2 API version for meta-data and       |
++|                | user-data.                                               |
+++----------------+----------------------------------------------------------+
++
+ 
+ To see which versions are supported from your cloud provider use the following
+ URL:
+@@ -71,7 +86,7 @@ configuration (in `/etc/cloud/cloud.cfg` or `/etc/cloud/cloud.cfg.d/`).
+ 
+ The settings that may be configured are:
+ 
+- * **metadata_urls**: This list of urls will be searched for an Ec2
++ * **metadata_urls**: This list of urls will be searched for an EC2
+    metadata service. The first entry that successfully returns a 200 response
+    for <url>/<version>/meta-data/instance-id will be selected.
+    (default: ['http://169.254.169.254', 'http://instance-data:8773']).
+@@ -121,4 +136,5 @@ Notes
+    For example: the primary NIC will have a DHCP route-metric of 100,
+    the next NIC will be 200.
+ 
++.. _EC2 tags user guide: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html#work-with-tags-in-IMDS
+ .. vi: textwidth=79
+diff --git a/tests/unittests/sources/test_ec2.py b/tests/unittests/sources/test_ec2.py
+index b376660d..7c8a5ea5 100644
+--- a/tests/unittests/sources/test_ec2.py
++++ b/tests/unittests/sources/test_ec2.py
+@@ -210,6 +210,17 @@ SECONDARY_IP_METADATA_2018_09_24 = {
+ 
+ M_PATH_NET = "cloudinit.sources.DataSourceEc2.net."
+ 
++TAGS_METADATA_2021_03_23 = {
++    **DEFAULT_METADATA,
++    "tags": {
++        "instance": {
++            "Environment": "production",
++            "Application": "test",
++            "TagWithoutValue": "",
++        }
++    },
++}
++
+ 
+ def _register_ssh_keys(rfunc, base_url, keys_data):
+     """handle ssh key inconsistencies.
+@@ -670,7 +681,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
+         logs_with_redacted = [log for log in all_logs if REDACT_TOK in log]
+         logs_with_token = [log for log in all_logs if "API-TOKEN" in log]
+         self.assertEqual(1, len(logs_with_redacted_ttl))
+-        self.assertEqual(81, len(logs_with_redacted))
++        self.assertEqual(83, len(logs_with_redacted))
+         self.assertEqual(0, len(logs_with_token))
+ 
+     @mock.patch("cloudinit.net.dhcp.maybe_perform_dhcp_discovery")
+@@ -811,6 +822,19 @@ class TestEc2(test_helpers.HttprettyTestCase):
+         )
+         self.assertIn("Crawl of metadata service took", self.logs.getvalue())
+ 
++    def test_get_instance_tags(self):
++        ds = self._setup_ds(
++            platform_data=self.valid_platform_data,
++            sys_cfg={"datasource": {"Ec2": {"strict_id": False}}},
++            md={"md": TAGS_METADATA_2021_03_23},
++        )
++        self.assertTrue(ds.get_data())
++        self.assertIn("tags", ds.metadata)
++        self.assertIn("instance", ds.metadata["tags"])
++        instance_tags = ds.metadata["tags"]["instance"]
++        self.assertEqual(instance_tags["Application"], "test")
++        self.assertEqual(instance_tags["Environment"], "production")
++
+ 
+ class TestGetSecondaryAddresses(test_helpers.CiTestCase):
+ 
+diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
+index ac157a2f..9f71ea0c 100644
+--- a/tools/.github-cla-signers
++++ b/tools/.github-cla-signers
+@@ -26,6 +26,7 @@ dermotbradley
+ dhensby
+ eandersson
+ eb3095
++edudobay
+ emmanuelthome
+ eslerm
+ esposem
+-- 
+2.27.0
+
diff --git a/SOURCES/ci-Use-Network-Manager-and-Netplan-as-default-renderers.patch b/SOURCES/ci-Use-Network-Manager-and-Netplan-as-default-renderers.patch
new file mode 100644
index 0000000..04d5e1f
--- /dev/null
+++ b/SOURCES/ci-Use-Network-Manager-and-Netplan-as-default-renderers.patch
@@ -0,0 +1,110 @@
+From 13ded463a6a0b1b0bf0dffc0a997f006dd25c4f3 Mon Sep 17 00:00:00 2001
+From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date: Thu, 19 May 2022 15:51:27 +0200
+Subject: [PATCH 2/4] Use Network-Manager and Netplan as default renderers for
+ RHEL and Fedora (#1465)
+
+RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+RH-MergeRequest: 57: Add native NetworkManager support (#1224)
+RH-Commit: [2/2] f2f977564bea496b0d76c0cef242959d03c2c73e
+RH-Bugzilla: 2059872
+RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
+RH-Acked-by: Jon Maloy <jmaloy@redhat.com>
+RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
+
+commit 7703aa98b89c8daba207c28a0422268ead10019a
+Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+Date:   Thu May 19 15:05:01 2022 +0200
+
+    Use Network-Manager and Netplan as default renderers for RHEL and Fedora (#1465)
+
+    This is adapted from Neal Gompa's PR:
+    https://github.com/canonical/cloud-init/pull/1435
+
+    The only difference is that we are not modifying renderers.py (thus
+    modifying the priority of all distros), but just tweaking cloud.cfg to
+    apply this change to Fedora and RHEL. Other distros can optionally
+    add themselves afterwards.
+
+            net: Prefer Netplan and NetworkManager renderers by default
+
+            NetworkManager is used by default on a variety of Linux distributions,
+            and exists as a cross-distribution network management service.
+
+            Additionally, add information about the NetworkManager renderer to
+            the cloud-init documentation.
+
+            Because Netplan can be explicitly used to manage NetworkManager,
+            it needs to be preferred before NetworkManager.
+
+            This change is a follow-up to #1224, which added the native
+            NetworkManager renderer.
+            This patch has been deployed on Fedora's cloud-init package throughout
+            the development of Fedora Linux 36 to verify that it works.
+
+            This should also make it tremendously easier for Linux distributions
+            to use cloud-init because now a standard configuration is supported
+            by default.
+
+            Signed-off-by: Neal Gompa <ngompa13@gmail.com>
+
+    Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+
+Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
+---
+ config/cloud.cfg.tmpl             |  3 +++
+ doc/rtd/topics/network-config.rst | 12 +++++++++++-
+ 2 files changed, 14 insertions(+), 1 deletion(-)
+
+diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
+index fb4b456c..86beee3c 100644
+--- a/config/cloud.cfg.tmpl
++++ b/config/cloud.cfg.tmpl
+@@ -330,4 +330,7 @@ system_info:
+ {% elif variant in ["dragonfly"] %}
+    network:
+       renderers: ['freebsd']
++{% elif variant in ["rhel", "fedora"] %}
++   network:
++      renderers: ['netplan', 'network-manager', 'networkd', 'sysconfig', 'eni']
+ {% endif %}
+diff --git a/doc/rtd/topics/network-config.rst b/doc/rtd/topics/network-config.rst
+index c461a3fe..f503caab 100644
+--- a/doc/rtd/topics/network-config.rst
++++ b/doc/rtd/topics/network-config.rst
+@@ -188,6 +188,15 @@ generated configuration into an internal network configuration state. From
+ this state `Cloud-init`_ delegates rendering of the configuration to Distro
+ supported formats.  The following ``renderers`` are supported in cloud-init:
+ 
++- **NetworkManager**
++
++`NetworkManager <https://networkmanager.dev>`_ is the standard Linux network
++configuration tool suite. It supports a wide range of networking setups.
++Configuration is typically stored in ``/etc/NetworkManager``.
++
++It is the default for a number of Linux distributions, notably Fedora;
++CentOS/RHEL; and derivatives.
++
+ - **ENI**
+ 
+ /etc/network/interfaces or ``ENI`` is supported by the ``ifupdown`` package
+@@ -215,6 +224,7 @@ is as follows:
+ - ENI
+ - Sysconfig
+ - Netplan
++- NetworkManager
+ 
+ When applying the policy, `Cloud-init`_ checks if the current instance has the
+ correct binaries and paths to support the renderer.  The first renderer that
+@@ -223,7 +233,7 @@ supplying an updated configuration in cloud-config. ::
+ 
+   system_info:
+     network:
+-      renderers: ['netplan', 'eni', 'sysconfig', 'freebsd', 'netbsd', 'openbsd']
++      renderers: ['netplan', 'network-manager', 'eni', 'sysconfig', 'freebsd', 'netbsd', 'openbsd']
+ 
+ 
+ Network Configuration Tools
+-- 
+2.35.3
+
diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec
index bc0729b..d171a71 100644
--- a/SPECS/cloud-init.spec
+++ b/SPECS/cloud-init.spec
@@ -6,7 +6,7 @@
 
 Name:           cloud-init
 Version:        22.1
-Release:        1%{?dist}
+Release:        3%{?dist}
 Summary:        Cloud instance init scripts
 
 Group:          System Environment/Base
@@ -21,6 +21,16 @@ Patch0003: 0003-limit-permissions-on-def_log_file.patch
 Patch0004: 0004-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch
 Patch0005: 0005-Remove-race-condition-between-cloud-init-and-Network.patch
 Patch0006: 0006-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch
+# For bz#2059872 - [RHEL-8]Rebase cloud-init from Fedora so it can configure networking using NM keyfiles
+Patch7: ci-Add-native-NetworkManager-support-1224.patch
+# For bz#2059872 - [RHEL-8]Rebase cloud-init from Fedora so it can configure networking using NM keyfiles
+Patch8: ci-Use-Network-Manager-and-Netplan-as-default-renderers.patch
+# For bz#2082071 - Align cloud.cfg file and systemd with cloud-init upstream .tmpl files
+Patch9: ci-Align-rhel-custom-files-with-upstream-1431.patch
+# For bz#2082071 - Align cloud.cfg file and systemd with cloud-init upstream .tmpl files
+Patch10: ci-Remove-rhel-specific-files.patch
+# For bz#2082686 - [cloud][init] Add support for reading tags from instance metadata
+Patch11: ci-Support-EC2-tags-in-instance-metadata-1309.patch
 
 BuildArch:      noarch
 
@@ -96,8 +106,6 @@ sed -i -e 's|#!/usr/bin/env python|#!/usr/bin/env python3|' \
 %install
 %py3_install --
 
-python3 tools/render-cloudcfg --variant fedora > $RPM_BUILD_ROOT/%{_sysconfdir}/cloud/cloud.cfg
-
 sed -i "s,@@PACKAGED_VERSION@@,%{version}-%{release}," $RPM_BUILD_ROOT/%{python3_sitelib}/cloudinit/version.py
 
 mkdir -p $RPM_BUILD_ROOT/var/lib/cloud
@@ -107,9 +115,6 @@ mkdir -p $RPM_BUILD_ROOT/run/cloud-init
 mkdir -p $RPM_BUILD_ROOT/%{_tmpfilesdir}
 cp -p %{SOURCE1} $RPM_BUILD_ROOT/%{_tmpfilesdir}/%{name}.conf
 
-# We supply our own config file since our software differs from Ubuntu's.
-cp -p rhel/cloud.cfg $RPM_BUILD_ROOT/%{_sysconfdir}/cloud/cloud.cfg
-
 mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/rsyslog.d
 cp -p tools/21-cloudinit.conf $RPM_BUILD_ROOT/%{_sysconfdir}/rsyslog.d/21-cloudinit.conf
 
@@ -117,17 +122,10 @@ cp -p tools/21-cloudinit.conf $RPM_BUILD_ROOT/%{_sysconfdir}/rsyslog.d/21-cloudi
 mv $RPM_BUILD_ROOT/etc/NetworkManager/dispatcher.d/hook-network-manager \
    $RPM_BUILD_ROOT/etc/NetworkManager/dispatcher.d/cloud-init-azure-hook
 
-# Install our own systemd units (rhbz#1440831)
-mkdir -p $RPM_BUILD_ROOT%{_unitdir}
-cp rhel/systemd/* $RPM_BUILD_ROOT%{_unitdir}/
-
 [ ! -d $RPM_BUILD_ROOT/usr/lib/systemd/system-generators ] && mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system-generators
 python3 tools/render-cloudcfg --variant rhel systemd/cloud-init-generator.tmpl > $RPM_BUILD_ROOT/usr/lib/systemd/system-generators/cloud-init-generator
 chmod 755 $RPM_BUILD_ROOT/usr/lib/systemd/system-generators/cloud-init-generator
 
-[ ! -d $RPM_BUILD_ROOT/usr/lib/%{name} ] && mkdir -p $RPM_BUILD_ROOT/usr/lib/%{name}
-cp -p tools/ds-identify $RPM_BUILD_ROOT%{_libexecdir}/%{name}/ds-identify
-
 # installing man pages
 mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man1/
 for man in cloud-id.1 cloud-init.1 cloud-init-per.1; do
@@ -183,7 +181,6 @@ fi
 
 %files
 %license LICENSE
-%doc ChangeLog rhel/README.rhel
 %config(noreplace) %{_sysconfdir}/cloud/cloud.cfg
 %dir               %{_sysconfdir}/cloud/cloud.cfg.d
 %config(noreplace) %{_sysconfdir}/cloud/cloud.cfg.d/*.cfg
@@ -193,6 +190,8 @@ fi
 %{_unitdir}/cloud-config.service
 %{_unitdir}/cloud-config.target
 %{_unitdir}/cloud-final.service
+%{_unitdir}/cloud-init-hotplugd.service
+%{_unitdir}/cloud-init-hotplugd.socket
 %{_unitdir}/cloud-init-local.service
 %{_unitdir}/cloud-init.service
 %{_unitdir}/cloud-init.target
@@ -205,10 +204,10 @@ fi
 %dir %verify(not mode) /run/cloud-init
 %dir /var/lib/cloud
 /etc/NetworkManager/dispatcher.d/cloud-init-azure-hook
-%{_udevrulesdir}/66-azure-ephemeral.rules
-%{_sysconfdir}/bash_completion.d/cloud-init
+/etc/dhcp/dhclient-exit-hooks.d/hook-dhclient
+/lib/udev/rules.d/66-azure-ephemeral.rules
+%{_datadir}/bash-completion/completions/cloud-init
 %{_bindir}/cloud-id
-%{_libexecdir}/%{name}/ds-identify
 /usr/lib/systemd/system-generators/cloud-init-generator
 %{_sysconfdir}/systemd/system/sshd-keygen@.service.d/disable-sshd-keygen-if-cloud-init-active.conf
 
@@ -217,6 +216,21 @@ fi
 %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf
 
 %changelog
+* Thu Jun 23 2022 Jon Maloy <jmaloy@redhat.com> - 22.1-3
+- ci-Support-EC2-tags-in-instance-metadata-1309.patch [bz#2082686]
+- Resolves: bz#2082686
+  ([cloud][init] Add support for reading tags from instance metadata)
+
+* Tue May 31 2022 Jon Maloy <jmaloy@redhat.com> - 22.1-2
+- ci-Add-native-NetworkManager-support-1224.patch [bz#2059872]
+- ci-Use-Network-Manager-and-Netplan-as-default-renderers.patch [bz#2059872]
+- ci-Align-rhel-custom-files-with-upstream-1431.patch [bz#2082071]
+- ci-Remove-rhel-specific-files.patch [bz#2082071]
+- Resolves: bz#2059872
+  ([RHEL-8]Rebase cloud-init from Fedora so it can configure networking using NM keyfiles)
+- Resolves: bz#2082071
+  (Align cloud.cfg file and systemd with cloud-init upstream .tmpl files)
+
 * Mon Apr 25 2022 Amy Chen <xiachen@redhat.com> - 22.1-1
 - Rebaes to 22.1 [bz#2065544]
 - Resolves: bz#2065544