commit ac771dc28eaa0b5cb9282562bd3a59dc6340eddd Author: CentOS Sources Date: Tue May 16 06:05:04 2023 +0000 import cloud-init-22.1-8.el8 diff --git a/.cloud-init.metadata b/.cloud-init.metadata new file mode 100644 index 0000000..0356978 --- /dev/null +++ b/.cloud-init.metadata @@ -0,0 +1 @@ +830185bb5ce87ad86e4d1c0c62329bb255ec1648 SOURCES/cloud-init-22.1.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf19bdd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/cloud-init-22.1.tar.gz diff --git a/SOURCES/0001-Add-initial-redhat-setup.patch b/SOURCES/0001-Add-initial-redhat-setup.patch new file mode 100644 index 0000000..d93c32c --- /dev/null +++ b/SOURCES/0001-Add-initial-redhat-setup.patch @@ -0,0 +1,586 @@ +From 5e1e568d7085fd4443b4e3ccc492f5e31747e270 Mon Sep 17 00:00:00 2001 +From: Amy Chen +Date: Wed, 20 Apr 2022 10:59:48 +0800 +Subject: Add initial redhat setup + +Merged patches (22.1) +- d18029bf Add netifaces package as a Requires in cloud-init.spec.template +- 31adf961 Add gdisk and openssl as deps to fix UEFI / Azure initialization +- f4a2905d Add dhcp-client as a dependency +- 290e14cc cloud-init.spec.template: update %systemd_postun parameter +- 9be4ae9b (tag: cloud-init-21.1-1.el8) Update to cloud-init-21.1-1.el8 + +Conflicts: +cloudinit/config/cc_chef.py Using double quotes instead of single quotes + +cloudinit/settings.py +- Using rhel settings +- Using double quotes instead of single quotes + +setup.py +- Following the changes of 21.1 rebase +- Using double quotes instead of single quotes + +redhat/cloud-init.spec.template +- Add the drop-in to the right cloud-init.spec used by our package builder, which is downstream-only part of the bz 2002492 fix. + +redhat/Makefile.common +- Backport the build handling fixes from patch "Update to cloud-init-21.1-1.el8" + +Signed-off-by: Amy Chen + +Merged patches (21.1): +- 915d30ad Change gating file to correct rhel version +- 311f318d Removing net-tools dependency +- 74731806 Adding man pages to Red Hat spec file +- 758d333d Removing blocking test from yaml configuration file +- c7e7c59c Changing permission of cloud-init-generator to 755 +- 8b85abbb Installing man pages in the correct place with correct permissions +- c6808d8d Fix unit failure of cloud-final.service if NetworkManager was not present. +- 11866ef6 Report full specific version with "cloud-init --version" + +Rebase notes (18.5): +- added bash_completition file +- added cloud-id file + +Merged patches (20.3): +- 01900d0 changing ds-identify patch from /usr/lib to /usr/libexec +- 7f47ca3 Render the generator from template instead of cp + +Merged patches (19.4): +- 4ab5a61 Fix for network configuration not persisting after reboot +- 84cf125 Removing cloud-user from wheel +- 31290ab Adding gating tests for Azure, ESXi and AWS + +Merged patches (18.5): +- 2d6b469 add power-state-change module to cloud_final_modules +- 764159f Adding systemd mount options to wait for cloud-init +- da4d99e Adding disk_setup to rhel/cloud.cfg +- f5c6832 Enable cloud-init by default on vmware + +Conflicts: +cloudinit/config/cc_chef.py: + - Updated header documentation text + - Replacing double quotes by simple quotes + +setup.py: + - Adding missing cmdclass info + +Signed-off-by: Eduardo Otubo +--- + .gitignore | 1 + + cloudinit/config/cc_chef.py | 65 ++- + cloudinit/settings.py | 7 +- + redhat/.gitignore | 1 + + redhat/Makefile | 71 +++ + redhat/Makefile.common | 37 ++ + redhat/cloud-init-tmpfiles.conf | 1 + + redhat/cloud-init.spec.template | 696 ++++++++++++++++++++++++++ + redhat/gating.yaml | 8 + + redhat/rpmbuild/BUILD/.gitignore | 3 + + redhat/rpmbuild/RPMS/.gitignore | 3 + + redhat/rpmbuild/SOURCES/.gitignore | 3 + + redhat/rpmbuild/SPECS/.gitignore | 3 + + redhat/rpmbuild/SRPMS/.gitignore | 3 + + redhat/scripts/frh.py | 25 + + redhat/scripts/git-backport-diff | 327 ++++++++++++ + redhat/scripts/git-compile-check | 215 ++++++++ + redhat/scripts/process-patches.sh | 92 ++++ + redhat/scripts/tarball_checksum.sh | 3 + + 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 | 25 + + rhel/systemd/cloud-init.target | 7 + + setup.py | 28 +- + tools/read-version | 28 +- + 30 files changed, 1756 insertions(+), 55 deletions(-) + create mode 100644 redhat/.gitignore + create mode 100644 redhat/Makefile + create mode 100644 redhat/Makefile.common + create mode 100644 redhat/cloud-init-tmpfiles.conf + create mode 100644 redhat/cloud-init.spec.template + create mode 100644 redhat/gating.yaml + create mode 100644 redhat/rpmbuild/BUILD/.gitignore + create mode 100644 redhat/rpmbuild/RPMS/.gitignore + create mode 100644 redhat/rpmbuild/SOURCES/.gitignore + create mode 100644 redhat/rpmbuild/SPECS/.gitignore + create mode 100644 redhat/rpmbuild/SRPMS/.gitignore + create mode 100755 redhat/scripts/frh.py + create mode 100755 redhat/scripts/git-backport-diff + create mode 100755 redhat/scripts/git-compile-check + create mode 100755 redhat/scripts/process-patches.sh + create mode 100755 redhat/scripts/tarball_checksum.sh + create mode 100644 rhel/README.rhel + create mode 100644 rhel/cloud-init-tmpfiles.conf + create mode 100644 rhel/cloud.cfg + create mode 100644 rhel/systemd/cloud-config.service + create mode 100644 rhel/systemd/cloud-config.target + create mode 100644 rhel/systemd/cloud-final.service + create mode 100644 rhel/systemd/cloud-init-local.service + create mode 100644 rhel/systemd/cloud-init.service + create mode 100644 rhel/systemd/cloud-init.target + +diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py +index fdb3a6e3..d028c548 100644 +--- a/cloudinit/config/cc_chef.py ++++ b/cloudinit/config/cc_chef.py +@@ -6,7 +6,70 @@ + # + # This file is part of cloud-init. See LICENSE file for license information. + +-"""Chef: module that configures, starts and installs chef.""" ++""" ++Chef ++---- ++**Summary:** module that configures, starts and installs chef. ++ ++This module enables chef to be installed (from packages or ++from gems, or from omnibus). Before this occurs chef configurations are ++written to disk (validation.pem, client.pem, firstboot.json, client.rb), ++and needed chef folders/directories are created (/etc/chef and /var/log/chef ++and so-on). Then once installing proceeds correctly if configured chef will ++be started (in daemon mode or in non-daemon mode) and then once that has ++finished (if ran in non-daemon mode this will be when chef finishes ++converging, if ran in daemon mode then no further actions are possible since ++chef will have forked into its own process) then a post run function can ++run that can do finishing activities (such as removing the validation pem ++file). ++ ++**Internal name:** ``cc_chef`` ++ ++**Module frequency:** per always ++ ++**Supported distros:** all ++ ++**Config keys**:: ++ ++ chef: ++ directories: (defaulting to /etc/chef, /var/log/chef, /var/lib/chef, ++ /var/cache/chef, /var/backups/chef, /run/chef) ++ validation_cert: (optional string to be written to file validation_key) ++ special value 'system' means set use existing file ++ validation_key: (optional the path for validation_cert. default ++ /etc/chef/validation.pem) ++ firstboot_path: (path to write run_list and initial_attributes keys that ++ should also be present in this configuration, defaults ++ to /etc/chef/firstboot.json) ++ exec: boolean to run or not run chef (defaults to false, unless ++ a gem installed is requested ++ where this will then default ++ to true) ++ ++ chef.rb template keys (if falsey, then will be skipped and not ++ written to /etc/chef/client.rb) ++ ++ chef: ++ client_key: ++ encrypted_data_bag_secret: ++ environment: ++ file_backup_path: ++ file_cache_path: ++ json_attribs: ++ log_level: ++ log_location: ++ node_name: ++ omnibus_url: ++ omnibus_url_retries: ++ omnibus_version: ++ pid_file: ++ server_url: ++ show_time: ++ ssl_verify_mode: ++ validation_cert: ++ validation_key: ++ validation_name: ++""" + + import itertools + import json +diff --git a/cloudinit/settings.py b/cloudinit/settings.py +index ecc1403b..39650a5b 100644 +--- a/cloudinit/settings.py ++++ b/cloudinit/settings.py +@@ -50,13 +50,16 @@ CFG_BUILTIN = { + ], + "def_log_file": "/var/log/cloud-init.log", + "log_cfgs": [], +- "syslog_fix_perms": ["syslog:adm", "root:adm", "root:wheel", "root:root"], ++ "mount_default_fields": [None, None, "auto", "defaults,nofail", "0", "2"], ++ "ssh_deletekeys": False, ++ "ssh_genkeytypes": [], ++ "syslog_fix_perms": [], + "system_info": { + "paths": { + "cloud_dir": "/var/lib/cloud", + "templates_dir": "/etc/cloud/templates/", + }, +- "distro": "ubuntu", ++ "distro": "rhel", + "network": {"renderers": None}, + }, + "vendor_data": {"enabled": True, "prefix": []}, +diff --git a/rhel/README.rhel b/rhel/README.rhel +new file mode 100644 +index 00000000..aa29630d +--- /dev/null ++++ b/rhel/README.rhel +@@ -0,0 +1,5 @@ ++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 +new file mode 100644 +index 00000000..0c6d2a3b +--- /dev/null ++++ b/rhel/cloud-init-tmpfiles.conf +@@ -0,0 +1 @@ ++d /run/cloud-init 0700 root root - - +diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg +new file mode 100644 +index 00000000..82e8bf62 +--- /dev/null ++++ b/rhel/cloud.cfg +@@ -0,0 +1,69 @@ ++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: 0 ++ssh_genkeytypes: ~ ++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 +new file mode 100644 +index 00000000..f3dcd4be +--- /dev/null ++++ b/rhel/systemd/cloud-config.service +@@ -0,0 +1,18 @@ ++[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 +new file mode 100644 +index 00000000..ae9b7d02 +--- /dev/null ++++ b/rhel/systemd/cloud-config.target +@@ -0,0 +1,11 @@ ++# 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 +new file mode 100644 +index 00000000..e281c0cf +--- /dev/null ++++ b/rhel/systemd/cloud-final.service +@@ -0,0 +1,24 @@ ++[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 +new file mode 100644 +index 00000000..8f9f6c9f +--- /dev/null ++++ b/rhel/systemd/cloud-init-local.service +@@ -0,0 +1,31 @@ ++[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 +new file mode 100644 +index 00000000..d0023a05 +--- /dev/null ++++ b/rhel/systemd/cloud-init.service +@@ -0,0 +1,25 @@ ++[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 ++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 +new file mode 100644 +index 00000000..083c3b6f +--- /dev/null ++++ b/rhel/systemd/cloud-init.target +@@ -0,0 +1,7 @@ ++# 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 a9132d2c..3c377eaa 100755 +--- a/setup.py ++++ b/setup.py +@@ -139,21 +139,6 @@ 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 = { +@@ -163,10 +148,6 @@ 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()]) +@@ -281,15 +262,13 @@ data_files = [ + ( + USR_LIB_EXEC + "/cloud-init", + [ +- "tools/ds-identify", + "tools/hook-hotplug", + "tools/uncloud-init", + "tools/write-ssh-key-fingerprints", + ], + ), + ( +- USR + "/share/bash-completion/completions", +- ["bash_completion/cloud-init"], ++ ETC + "/bash_completion.d", ["bash_completion/cloud-init"], + ), + (USR + "/share/doc/cloud-init", [f for f in glob("doc/*") if is_f(f)]), + ( +@@ -308,8 +287,7 @@ if not platform.system().endswith("BSD"): + ETC + "/NetworkManager/dispatcher.d/", + ["tools/hook-network-manager"], + ), +- (ETC + "/dhcp/dhclient-exit-hooks.d/", ["tools/hook-dhclient"]), +- (LIB + "/udev/rules.d", [f for f in glob("udev/*.rules")]), ++ ("/usr/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"], +@@ -339,8 +317,6 @@ 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", +diff --git a/tools/read-version b/tools/read-version +index 02c90643..79755f78 100755 +--- a/tools/read-version ++++ b/tools/read-version +@@ -71,32 +71,8 @@ version_long = None + is_release_branch_ci = ( + os.environ.get("TRAVIS_PULL_REQUEST_BRANCH", "").startswith("upstream/") + ) +-if is_gitdir(_tdir) and which("git") and not is_release_branch_ci: +- flags = [] +- if use_tags: +- flags = ['--tags'] +- cmd = ['git', 'describe', '--abbrev=8', '--match=[0-9]*'] + flags +- +- try: +- version = tiny_p(cmd).strip() +- except RuntimeError: +- version = None +- +- if version is None or not version.startswith(src_version): +- sys.stderr.write("git describe version (%s) differs from " +- "cloudinit.version (%s)\n" % (version, src_version)) +- sys.stderr.write( +- "Please get the latest upstream tags.\n" +- "As an example, this can be done with the following:\n" +- "$ git remote add upstream https://git.launchpad.net/cloud-init\n" +- "$ git fetch upstream --tags\n" +- ) +- sys.exit(1) +- +- version_long = tiny_p(cmd + ["--long"]).strip() +-else: +- version = src_version +- version_long = None ++version = src_version ++version_long = None + + # version is X.Y.Z[+xxx.gHASH] + # version_long is None or X.Y.Z-xxx-gHASH +-- +2.31.1 + diff --git a/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch b/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch new file mode 100644 index 0000000..60c0a2a --- /dev/null +++ b/SOURCES/0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch @@ -0,0 +1,261 @@ +From e0dc628ac553072891fa6607dc91b652efd99be2 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Fri, 7 May 2021 13:36:06 +0200 +Subject: Do not write NM_CONTROLLED=no in generated interface config files + +Conflicts 20.3: + - Not appplying patch on cloudinit/net/sysconfig.py since it now has a +mechanism to identify if cloud-init is running on RHEL, having the +correct settings for NM_CONTROLLED. + +X-downstream-only: true +Signed-off-by: Eduardo Otubo +Signed-off-by: Ryan McCabe +--- + cloudinit/net/sysconfig.py | 1 - + tests/unittests/test_net.py | 28 ---------------------------- + 2 files changed, 29 deletions(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index ba85c4f6..e06ddee7 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -336,7 +336,6 @@ class Renderer(renderer.Renderer): + "rhel": { + "ONBOOT": True, + "USERCTL": False, +- "NM_CONTROLLED": False, + "BOOTPROTO": "none", + }, + "suse": {"BOOTPROTO": "static", "STARTMODE": "auto"}, +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index 47e4ba00..591241b3 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -579,7 +579,6 @@ GATEWAY=172.19.3.254 + HWADDR=fa:16:3e:ed:9a:59 + IPADDR=172.19.1.34 + NETMASK=255.255.252.0 +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -712,7 +711,6 @@ IPADDR=172.19.1.34 + IPADDR1=10.0.0.10 + NETMASK=255.255.252.0 + NETMASK1=255.255.255.0 +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -874,7 +872,6 @@ IPV6_AUTOCONF=no + IPV6_DEFAULTGW=2001:DB8::1 + IPV6_FORCE_ACCEPT_RA=no + NETMASK=255.255.252.0 +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -1053,7 +1050,6 @@ NETWORK_CONFIGS = { + BOOTPROTO=none + DEVICE=eth1 + HWADDR=cf:d6:af:48:e8:80 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""" +@@ -1072,7 +1068,6 @@ NETWORK_CONFIGS = { + IPADDR=192.168.21.3 + NETMASK=255.255.255.0 + METRIC=10000 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""" +@@ -1244,7 +1239,6 @@ NETWORK_CONFIGS = { + IPV6_AUTOCONF=no + IPV6_FORCE_ACCEPT_RA=no + NETMASK=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -2093,7 +2087,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DHCPV6C=yes + IPV6INIT=yes + MACADDR=aa:bb:cc:dd:ee:ff +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Bond + USERCTL=no""" +@@ -2103,7 +2096,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + BOOTPROTO=dhcp + DEVICE=bond0.200 + DHCLIENT_SET_DEFAULT_ROUTE=no +- NM_CONTROLLED=no + ONBOOT=yes + PHYSDEV=bond0 + USERCTL=no +@@ -2123,7 +2115,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + IPV6_DEFAULTGW=2001:4800:78ff:1b::1 + MACADDR=bb:bb:bb:bb:bb:aa + NETMASK=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=yes + PRIO=22 + STP=no +@@ -2135,7 +2126,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + BOOTPROTO=none + DEVICE=eth0 + HWADDR=c0:d6:9f:2c:e8:80 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""" +@@ -2154,7 +2144,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + MTU=1500 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=yes + PHYSDEV=eth0 + USERCTL=no +@@ -2166,7 +2155,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEVICE=eth1 + HWADDR=aa:d6:9f:2c:e8:80 + MASTER=bond0 +- NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes + TYPE=Ethernet +@@ -2178,7 +2166,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEVICE=eth2 + HWADDR=c0:bb:9f:2c:e8:80 + MASTER=bond0 +- NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes + TYPE=Ethernet +@@ -2190,7 +2177,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + BRIDGE=br0 + DEVICE=eth3 + HWADDR=66:bb:9f:2c:e8:80 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""" +@@ -2201,7 +2187,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + BRIDGE=br0 + DEVICE=eth4 + HWADDR=98:bb:9f:2c:e8:80 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""" +@@ -2212,7 +2197,6 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true + DEVICE=eth5 + DHCLIENT_SET_DEFAULT_ROUTE=no + HWADDR=98:bb:9f:2c:e8:8a +- NM_CONTROLLED=no + ONBOOT=no + TYPE=Ethernet + USERCTL=no""" +@@ -2689,7 +2673,6 @@ iface bond0 inet6 static + MTU=9000 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Bond + USERCTL=no +@@ -2701,7 +2684,6 @@ iface bond0 inet6 static + DEVICE=bond0s0 + HWADDR=aa:bb:cc:dd:e8:00 + MASTER=bond0 +- NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes + TYPE=Ethernet +@@ -2729,7 +2711,6 @@ iface bond0 inet6 static + DEVICE=bond0s1 + HWADDR=aa:bb:cc:dd:e8:01 + MASTER=bond0 +- NM_CONTROLLED=no + ONBOOT=yes + SLAVE=yes + TYPE=Ethernet +@@ -2794,7 +2775,6 @@ iface bond0 inet6 static + BOOTPROTO=none + DEVICE=en0 + HWADDR=aa:bb:cc:dd:e8:00 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no""" +@@ -2815,7 +2795,6 @@ iface bond0 inet6 static + MTU=2222 + NETMASK=255.255.255.0 + NETMASK1=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=yes + PHYSDEV=en0 + USERCTL=no +@@ -2890,7 +2869,6 @@ iface bond0 inet6 static + DEVICE=br0 + IPADDR=192.168.2.2 + NETMASK=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=yes + PRIO=22 + STP=no +@@ -3032,7 +3010,6 @@ iface bond0 inet6 static + HWADDR=52:54:00:12:34:00 + IPADDR=192.168.1.2 + NETMASK=255.255.255.0 +- NM_CONTROLLED=no + ONBOOT=no + TYPE=Ethernet + USERCTL=no +@@ -3044,7 +3021,6 @@ iface bond0 inet6 static + DEVICE=eth1 + HWADDR=52:54:00:12:34:aa + MTU=1480 +- NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -3055,7 +3031,6 @@ iface bond0 inet6 static + BOOTPROTO=none + DEVICE=eth2 + HWADDR=52:54:00:12:34:ff +- NM_CONTROLLED=no + ONBOOT=no + TYPE=Ethernet + USERCTL=no +@@ -3628,7 +3603,6 @@ class TestRhelSysConfigRendering(CiTestCase): + BOOTPROTO=dhcp + DEVICE=eth1000 + HWADDR=07-1c-c6-75-a4-be +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -3840,7 +3814,6 @@ GATEWAY=10.0.2.2 + HWADDR=52:54:00:12:34:00 + IPADDR=10.0.2.15 + NETMASK=255.255.255.0 +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +@@ -3910,7 +3883,6 @@ USERCTL=no + # + BOOTPROTO=dhcp + DEVICE=eth0 +-NM_CONTROLLED=no + ONBOOT=yes + TYPE=Ethernet + USERCTL=no +-- +2.31.1 + diff --git a/SOURCES/0003-limit-permissions-on-def_log_file.patch b/SOURCES/0003-limit-permissions-on-def_log_file.patch new file mode 100644 index 0000000..6f58247 --- /dev/null +++ b/SOURCES/0003-limit-permissions-on-def_log_file.patch @@ -0,0 +1,69 @@ +From cb7b35ca10c82c9725c3527e3ec5fb8cb7c61bc0 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Fri, 7 May 2021 13:36:08 +0200 +Subject: limit permissions on def_log_file + +This sets a default mode of 0600 on def_log_file, and makes this +configurable via the def_log_file_mode option in cloud.cfg. + +LP: #1541196 +Resolves: rhbz#1424612 +X-approved-upstream: true + +Conflicts 21.1: + cloudinit/stages.py: adjusting call of ensure_file() to use more +recent version + +Signed-off-by: Eduardo Otubo +--- + cloudinit/settings.py | 1 + + cloudinit/stages.py | 1 + + doc/examples/cloud-config.txt | 4 ++++ + 3 files changed, 6 insertions(+) + +diff --git a/cloudinit/settings.py b/cloudinit/settings.py +index 39650a5b..3c2145e9 100644 +--- a/cloudinit/settings.py ++++ b/cloudinit/settings.py +@@ -49,6 +49,7 @@ CFG_BUILTIN = { + "None", + ], + "def_log_file": "/var/log/cloud-init.log", ++ "def_log_file_mode": 0o600, + "log_cfgs": [], + "mount_default_fields": [None, None, "auto", "defaults,nofail", "0", "2"], + "ssh_deletekeys": False, +diff --git a/cloudinit/stages.py b/cloudinit/stages.py +index 3f17294b..61db1dbd 100644 +--- a/cloudinit/stages.py ++++ b/cloudinit/stages.py +@@ -205,6 +205,7 @@ class Init(object): + def _initialize_filesystem(self): + util.ensure_dirs(self._initial_subdirs()) + log_file = util.get_cfg_option_str(self.cfg, "def_log_file") ++ log_file_mode = util.get_cfg_option_int(self.cfg, "def_log_file_mode") + if log_file: + util.ensure_file(log_file, mode=0o640, preserve_mode=True) + perms = self.cfg.get("syslog_fix_perms") +diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt +index a2b4a3fa..0ccf3147 100644 +--- a/doc/examples/cloud-config.txt ++++ b/doc/examples/cloud-config.txt +@@ -414,10 +414,14 @@ timezone: US/Eastern + # if syslog_fix_perms is a list, it will iterate through and use the + # first pair that does not raise error. + # ++# 'def_log_file' will be created with mode 'def_log_file_mode', which ++# is specified as a numeric value and defaults to 0600. ++# + # the default values are '/var/log/cloud-init.log' and 'syslog:adm' + # the value of 'def_log_file' should match what is configured in logging + # if either is empty, then no change of ownership will be done + def_log_file: /var/log/my-logging-file.log ++def_log_file_mode: 0600 + syslog_fix_perms: syslog:root + + # you can set passwords for a user or multiple users +-- +2.31.1 + diff --git a/SOURCES/0004-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch b/SOURCES/0004-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch new file mode 100644 index 0000000..5c5a144 --- /dev/null +++ b/SOURCES/0004-include-NOZEROCONF-yes-in-etc-sysconfig-network.patch @@ -0,0 +1,52 @@ +From ffa647e83efd4293bd027e9e390274aad8a12d94 Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Fri, 7 May 2021 13:36:13 +0200 +Subject: include 'NOZEROCONF=yes' in /etc/sysconfig/network + +RH-Author: Eduardo Otubo +Message-id: <20190320114559.23708-1-otubo@redhat.com> +Patchwork-id: 84937 +O-Subject: [RHEL-7.7 cloud-init PATCH] include 'NOZEROCONF=yes' in /etc/sysconfig/network +Bugzilla: 1653131 +RH-Acked-by: Cathy Avery +RH-Acked-by: Mohammed Gamal +RH-Acked-by: Vitaly Kuznetsov + +The option NOZEROCONF=yes is not included by default in +/etc/sysconfig/network, which is required by Overcloud instances. The +patch also includes tests for the modifications. + +X-downstream-only: yes +Resolves: rhbz#1653131 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + cloudinit/net/sysconfig.py | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index e06ddee7..362e8d19 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -1038,7 +1038,16 @@ class Renderer(renderer.Renderer): + # Distros configuring /etc/sysconfig/network as a file e.g. Centos + if sysconfig_path.endswith("network"): + util.ensure_dir(os.path.dirname(sysconfig_path)) +- netcfg = [_make_header(), "NETWORKING=yes"] ++ netcfg = [] ++ for line in util.load_file(sysconfig_path, quiet=True).split("\n"): ++ if "cloud-init" in line: ++ break ++ if not line.startswith(("NETWORKING=", ++ "IPV6_AUTOCONF=", ++ "NETWORKING_IPV6=")): ++ netcfg.append(line) ++ # Now generate the cloud-init portion of sysconfig/network ++ netcfg.extend([_make_header(), "NETWORKING=yes"]) + if network_state.use_ipv6: + netcfg.append("NETWORKING_IPV6=yes") + netcfg.append("IPV6_AUTOCONF=no") +-- +2.31.1 + diff --git a/SOURCES/0005-Remove-race-condition-between-cloud-init-and-Network.patch b/SOURCES/0005-Remove-race-condition-between-cloud-init-and-Network.patch new file mode 100644 index 0000000..478e5ab --- /dev/null +++ b/SOURCES/0005-Remove-race-condition-between-cloud-init-and-Network.patch @@ -0,0 +1,148 @@ +From 386f0a82bfdfd62e506bf4251c17263260d3250a Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Fri, 7 May 2021 13:36:14 +0200 +Subject: Remove race condition between cloud-init and NetworkManager + +Message-id: <20200302104635.11648-1-otubo@redhat.com> +Patchwork-id: 94098 +O-Subject: [RHEL-7.9/RHEL-8.2.0 cloud-init PATCH] Remove race condition between cloud-init and NetworkManager +Bugzilla: 1807797 +RH-Acked-by: Cathy Avery +RH-Acked-by: Mohammed Gamal + +BZ: 1748015 +BRANCH: rhel7/master-18.5 +BREW: 26924611 + +BZ: 1807797 +BRANCH: rhel820/master-18.5 +BREW: 26924957 + +cloud-init service is set to start before NetworkManager service starts, +but this does not avoid a race condition between them. NetworkManager +starts before cloud-init can write `dns=none' to the file: +/etc/NetworkManager/conf.d/99-cloud-init.conf. This way NetworkManager +doesn't read the configuration and erases all resolv.conf values upon +shutdown. On the next reboot neither cloud-init or NetworkManager will +write anything to resolv.conf, leaving it blank. + +This patch introduces a NM reload (try-restart) at the end of cloud-init +start up so it won't erase resolv.conf upon first shutdown. + +x-downstream-only: yes +resolves: rhbz#1748015, rhbz#1807797 and rhbz#1804780 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina + +This commit is a squash and also includes the folloowing commits: + +commit 316a17b7c02a87fa9b2981535be0b20d165adc46 +Author: Eduardo Otubo +Date: Mon Jun 1 11:58:06 2020 +0200 + + Make cloud-init.service execute after network is up + + RH-Author: Eduardo Otubo + Message-id: <20200526090804.2047-1-otubo@redhat.com> + Patchwork-id: 96809 + O-Subject: [RHEL-8.2.1 cloud-init PATCH] Make cloud-init.service execute after network is up + Bugzilla: 1803928 + RH-Acked-by: Vitaly Kuznetsov + RH-Acked-by: Miroslav Rezanina + + cloud-init.service needs to wait until network is fully up before + continuing executing and configuring its service. + + Signed-off-by: Eduardo Otubo + + x-downstream-only: yes + Resolves: rhbz#1831646 + Signed-off-by: Miroslav Rezanina + +commit 0422ba0e773d1a8257a3f2bf3db05f3bc7917eb7 +Author: Eduardo Otubo +Date: Thu May 28 08:44:08 2020 +0200 + + Remove race condition between cloud-init and NetworkManager + + RH-Author: Eduardo Otubo + Message-id: <20200327121911.17699-1-otubo@redhat.com> + Patchwork-id: 94453 + O-Subject: [RHEL-7.9/RHEL-8.2.0 cloud-init PATCHv2] Remove race condition between cloud-init and NetworkManager + Bugzilla: 1840648 + RH-Acked-by: Vitaly Kuznetsov + RH-Acked-by: Miroslav Rezanina + RH-Acked-by: Cathy Avery + + cloud-init service is set to start before NetworkManager service starts, + but this does not avoid a race condition between them. NetworkManager + starts before cloud-init can write `dns=none' to the file: + /etc/NetworkManager/conf.d/99-cloud-init.conf. This way NetworkManager + doesn't read the configuration and erases all resolv.conf values upon + shutdown. On the next reboot neither cloud-init or NetworkManager will + write anything to resolv.conf, leaving it blank. + + This patch introduces a NM reload (try-reload-or-restart) at the end of cloud-init + start up so it won't erase resolv.conf upon first shutdown. + + x-downstream-only: yes + + Signed-off-by: Eduardo Otubo otubo@redhat.com + Signed-off-by: Miroslav Rezanina + +commit e0b48a936433faea7f56dbc29dda35acf7d375f7 +Author: Eduardo Otubo +Date: Thu May 28 08:44:06 2020 +0200 + + Enable ssh_deletekeys by default + + RH-Author: Eduardo Otubo + Message-id: <20200317091705.15715-1-otubo@redhat.com> + Patchwork-id: 94365 + O-Subject: [RHEL-7.9/RHEL-8.2.0 cloud-init PATCH] Enable ssh_deletekeys by default + Bugzilla: 1814152 + RH-Acked-by: Mohammed Gamal + RH-Acked-by: Vitaly Kuznetsov + + The configuration option ssh_deletekeys will trigger the generation + of new ssh keys for every new instance deployed. + + x-downstream-only: yes + resolves: rhbz#1814152 + + Signed-off-by: Eduardo Otubo + Signed-off-by: Miroslav Rezanina +--- + rhel/cloud.cfg | 2 +- + rhel/systemd/cloud-init.service | 1 + + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg +index 82e8bf62..9ecba215 100644 +--- a/rhel/cloud.cfg ++++ b/rhel/cloud.cfg +@@ -6,7 +6,7 @@ ssh_pwauth: 0 + + mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2'] + resize_rootfs_tmp: /dev +-ssh_deletekeys: 0 ++ssh_deletekeys: 1 + ssh_genkeytypes: ~ + syslog_fix_perms: ~ + disable_vmware_customization: false +diff --git a/rhel/systemd/cloud-init.service b/rhel/systemd/cloud-init.service +index d0023a05..0b3d796d 100644 +--- a/rhel/systemd/cloud-init.service ++++ b/rhel/systemd/cloud-init.service +@@ -5,6 +5,7 @@ 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 +-- +2.31.1 + diff --git a/SOURCES/0006-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch b/SOURCES/0006-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch new file mode 100644 index 0000000..e596836 --- /dev/null +++ b/SOURCES/0006-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch @@ -0,0 +1,65 @@ +From b545a0cbabe8924d048b7172b30e7aad59ed32d5 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Thu, 20 May 2021 08:53:55 +0200 +Subject: rhel/cloud.cfg: remove ssh_genkeytypes in settings.py and set in + cloud.cfg + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 10: rhel/cloud.cfg: remove ssh_genkeytypes in settings.py and set in cloud.cfg +RH-Commit: [1/1] 6da989423b9b6e017afbac2f1af3649b0487310f +RH-Bugzilla: 1957532 +RH-Acked-by: Eduardo Otubo +RH-Acked-by: Cathy Avery +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohamed Gamal Morsy + +Currently genkeytypes in cloud.cfg is set to None, so together with +ssh_deletekeys=1 cloudinit on first boot it will just delete the existing +keys and not generate new ones. + +Just removing that property in cloud.cfg is not enough, because +settings.py provides another empty default value that will be used +instead, resulting to no key generated even when the property is not defined. + +Removing genkeytypes also in settings.py will default to GENERATE_KEY_NAMES, +but since we want only 'rsa', 'ecdsa' and 'ed25519', add back genkeytypes in +cloud.cfg with the above defaults. + +Also remove ssh_deletekeys in settings.py as we always need +to 1 (and it also defaults to 1). + +Signed-off-by: Emanuele Giuseppe Esposito +--- + cloudinit/settings.py | 2 -- + rhel/cloud.cfg | 2 +- + 2 files changed, 1 insertion(+), 3 deletions(-) + +diff --git a/cloudinit/settings.py b/cloudinit/settings.py +index 3c2145e9..71672e10 100644 +--- a/cloudinit/settings.py ++++ b/cloudinit/settings.py +@@ -52,8 +52,6 @@ CFG_BUILTIN = { + "def_log_file_mode": 0o600, + "log_cfgs": [], + "mount_default_fields": [None, None, "auto", "defaults,nofail", "0", "2"], +- "ssh_deletekeys": False, +- "ssh_genkeytypes": [], + "syslog_fix_perms": [], + "system_info": { + "paths": { +diff --git a/rhel/cloud.cfg b/rhel/cloud.cfg +index 9ecba215..cbee197a 100644 +--- a/rhel/cloud.cfg ++++ b/rhel/cloud.cfg +@@ -7,7 +7,7 @@ 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: ~ ++ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519'] + syslog_fix_perms: ~ + disable_vmware_customization: false + +-- +2.31.1 + 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 +Date: Mon, 2 May 2022 14:21:24 +0200 +Subject: [PATCH 1/4] Add native NetworkManager support (#1224) + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 57: Add native NetworkManager support (#1224) +RH-Commit: [1/2] 56b9ed40840a4930c421c2749e8aa385097bef93 +RH-Bugzilla: 2059872 +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Jon Maloy +RH-Acked-by: Eduardo Otubo + +commit feda344e6cf9d37b09bc13cf333a717d1654c26c +Author: Lubomir Rintel +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 +--- + 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 ++# 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 or route, ensuring ++ the appropriate value gets used for . ++ """ ++ ++ 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 property. ++ """ ++ ++ value = subnet["address"] + "/" + str(subnet["prefix"]) ++ self._add_numbered(family, "address", value) ++ ++ def _add_route(self, family, route): ++ """ ++ Adds a ipv[46].route 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 +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 +RH-MergeRequest: 65: Align rhel custom files with upstream (#1431) +RH-Commit: [1/2] 5d9067175688b1006472a477b0916b81c73d5e07 +RH-Bugzilla: 2082071 +RH-Acked-by: Mohamed Gamal Morsy +RH-Acked-by: Eduardo Otubo +RH-Acked-by: Vitaly Kuznetsov + +commit 9624758f91b61f4711e8d7b5c83075b5d23e0c43 +Author: Emanuele Giuseppe Esposito +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 + + RHBZ: 2082071 + +Signed-off-by: Emanuele Giuseppe Esposito +--- + 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-Ensure-network-ready-before-cloud-init-service-runs-.patch b/SOURCES/ci-Ensure-network-ready-before-cloud-init-service-runs-.patch new file mode 100644 index 0000000..cc65e3e --- /dev/null +++ b/SOURCES/ci-Ensure-network-ready-before-cloud-init-service-runs-.patch @@ -0,0 +1,43 @@ +From df1c0f391537071c34652ee6df9bff87e5aea230 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Wed, 14 Dec 2022 09:20:47 +0100 +Subject: [PATCH] Ensure network ready before cloud-init service runs on RHEL + (#1893) + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 87: Ensure network ready before cloud-init service runs on RHEL (#1893) +RH-Bugzilla: 2151861 +RH-Acked-by: Mohamed Gamal Morsy +RH-Acked-by: Camilla Conte +RH-Commit: [1/1] 5bb5f6f94a205854633fb1606ccc68e838c2030d + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2151861 + +commit 6e725f36647407d201af0603d7db11fc96a93d4d +Author: James Falcon +Date: Tue Dec 13 10:55:23 2022 -0600 + + Ensure network ready before cloud-init service runs on RHEL (#1893) + + LP: #1998655 + +Signed-off-by: Emanuele Giuseppe Esposito +--- + systemd/cloud-init.service.tmpl | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/systemd/cloud-init.service.tmpl b/systemd/cloud-init.service.tmpl +index c170aef7..fc984d5c 100644 +--- a/systemd/cloud-init.service.tmpl ++++ b/systemd/cloud-init.service.tmpl +@@ -16,6 +16,7 @@ After=networking.service + "miraclelinux", "openEuler", "rhel", "rocky", "virtuozzo"] %} + After=network.service + After=NetworkManager.service ++After=NetworkManager-wait-online.service + {% endif %} + {% if variant in ["suse"] %} + After=wicked.service +-- +2.38.1 + 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 +Date: Thu, 19 May 2022 11:38:22 +0200 +Subject: [PATCH 4/4] Remove rhel specific files + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 65: Align rhel custom files with upstream (#1431) +RH-Commit: [2/2] 5e31f0bcb500682e7746ccbd2e628c2ef339d6c6 +RH-Bugzilla: 2082071 +RH-Acked-by: Mohamed Gamal Morsy +RH-Acked-by: Eduardo Otubo +RH-Acked-by: Vitaly Kuznetsov + +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 +--- + 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-Revert-Add-native-NetworkManager-support-1224.patch b/SOURCES/ci-Revert-Add-native-NetworkManager-support-1224.patch new file mode 100644 index 0000000..e4e3594 --- /dev/null +++ b/SOURCES/ci-Revert-Add-native-NetworkManager-support-1224.patch @@ -0,0 +1,2266 @@ +From f1836e78d20ef34b05b6aba002fc10a97eceb454 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Mon, 8 Aug 2022 10:08:50 +0200 +Subject: [PATCH 1/2] Revert "Add native NetworkManager support (#1224)" + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 81: Revert "Use Network-Manager and Netplan as default renderers for RHEL and Fedora (#1465)" +RH-Commit: [1/2] 5b3e51502a89c2dcfbc97dc08a86b792454fedd3 +RH-Bugzilla: 2107464 2110066 2117526 2104393 2098624 +RH-Acked-by: Eduardo Otubo +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohamed Gamal Morsy + +NM is not yet stable, so we don't want to support it for now. +This reverts commit 0d93e53fd05c44b62e3456b7580c9de8135e6b5a. + +Signed-off-by: Emanuele Giuseppe Esposito +--- + 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 | 1268 +++--------------------- + tests/unittests/test_net_activators.py | 93 +- + 7 files changed, 193 insertions(+), 1624 deletions(-) + delete mode 100644 cloudinit/net/network_manager.py + +diff --git a/cloudinit/cmd/devel/net_convert.py b/cloudinit/cmd/devel/net_convert.py +index 647fe07b..18b1e7ff 100755 +--- a/cloudinit/cmd/devel/net_convert.py ++++ b/cloudinit/cmd/devel/net_convert.py +@@ -7,14 +7,7 @@ import os + import sys + + from cloudinit import distros, log, safeyaml +-from cloudinit.net import ( +- eni, +- netplan, +- network_manager, +- network_state, +- networkd, +- sysconfig, +-) ++from cloudinit.net import eni, netplan, network_state, networkd, sysconfig + from cloudinit.sources import DataSourceAzure as azure + from cloudinit.sources import DataSourceOVF as ovf + from cloudinit.sources.helpers import openstack +@@ -81,7 +74,7 @@ def get_parser(parser=None): + parser.add_argument( + "-O", + "--output-kind", +- choices=["eni", "netplan", "networkd", "sysconfig", "network-manager"], ++ choices=["eni", "netplan", "networkd", "sysconfig"], + required=True, + help="The network config format to emit", + ) +@@ -155,9 +148,6 @@ 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 edbc0c06..e80c26df 100644 +--- a/cloudinit/net/activators.py ++++ b/cloudinit/net/activators.py +@@ -1,14 +1,15 @@ + # 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__) + +@@ -123,24 +124,20 @@ class IfUpDownActivator(NetworkActivator): + class NetworkManagerActivator(NetworkActivator): + @staticmethod + def available(target=None) -> bool: +- """Return true if NetworkManager can be used on this system.""" +- return nm_available(target=target) ++ """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) + + @staticmethod + def bring_up_interface(device_name: str) -> bool: +- """Bring up connection using nmcli. ++ """Bring up interface using nmcli. + + Return True is successful, otherwise return False + """ +- 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] ++ cmd = ["nmcli", "connection", "up", "ifname", device_name] + return _alter_interface(cmd, device_name) + + @staticmethod +@@ -149,7 +146,7 @@ class NetworkManagerActivator(NetworkActivator): + + Return True is successful, otherwise return False + """ +- cmd = ["nmcli", "device", "disconnect", device_name] ++ cmd = ["nmcli", "connection", "down", device_name] + return _alter_interface(cmd, device_name) + + +diff --git a/cloudinit/net/network_manager.py b/cloudinit/net/network_manager.py +deleted file mode 100644 +index 79b0fe0b..00000000 +--- a/cloudinit/net/network_manager.py ++++ /dev/null +@@ -1,377 +0,0 @@ +-# Copyright 2022 Red Hat, Inc. +-# +-# Author: Lubomir Rintel +-# 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 or route, ensuring +- the appropriate value gets used for . +- """ +- +- 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 property. +- """ +- +- value = subnet["address"] + "/" + str(subnet["prefix"]) +- self._add_numbered(family, "address", value) +- +- def _add_route(self, family, route): +- """ +- Adds a ipv[46].route 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 7edc34b5..c755f04c 100644 +--- a/cloudinit/net/renderers.py ++++ b/cloudinit/net/renderers.py +@@ -8,7 +8,6 @@ from . import ( + freebsd, + netbsd, + netplan, +- network_manager, + networkd, + openbsd, + renderer, +@@ -20,7 +19,6 @@ NAME_TO_RENDERER = { + "freebsd": freebsd, + "netbsd": netbsd, + "netplan": netplan, +- "network-manager": network_manager, + "networkd": networkd, + "openbsd": openbsd, + "sysconfig": sysconfig, +@@ -30,7 +28,6 @@ DEFAULT_PRIORITY = [ + "eni", + "sysconfig", + "netplan", +- "network-manager", + "freebsd", + "netbsd", + "openbsd", +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index c3b0c795..362e8d19 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -5,6 +5,8 @@ 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 +@@ -64,6 +66,24 @@ 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.""" + +@@ -1011,6 +1031,8 @@ 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 +@@ -1049,9 +1071,14 @@ def _supported_vlan_names(rdev, vid): + + + def available(target=None): +- if not util.system_info()["variant"] in KNOWN_DISTROS: +- return False ++ sysconfig = available_sysconfig(target=target) ++ nm = available_nm(target=target) ++ return util.system_info()["variant"] in KNOWN_DISTROS and any( ++ [nm, sysconfig] ++ ) ++ + ++def available_sysconfig(target=None): + expected = ["ifup", "ifdown"] + search = ["/sbin", "/usr/sbin"] + for p in expected: +@@ -1068,4 +1095,10 @@ def available(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 ef21ad76..591241b3 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -21,7 +21,6 @@ from cloudinit.net import ( + interface_has_own_mac, + natural_sort_key, + netplan, +- network_manager, + network_state, + networkd, + renderers, +@@ -612,37 +611,6 @@ 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": { +@@ -1105,50 +1073,6 @@ 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 +@@ -1221,34 +1145,6 @@ 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 +@@ -1351,37 +1247,6 @@ 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": { +@@ -1392,34 +1257,6 @@ 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 +@@ -1493,30 +1330,6 @@ 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( +@@ -1724,30 +1537,6 @@ 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( +@@ -1836,30 +1625,6 @@ 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( +@@ -1959,29 +1724,6 @@ 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 +@@ -2035,30 +1777,6 @@ 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 +@@ -2497,254 +2215,6 @@ 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 +@@ -2933,10 +2403,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:15::1 ++ - gateway: 3001:67c:1562:1 + network: 3001:67c:1 + netmask: "ffff:ffff::" + metric: 10000 +@@ -2981,10 +2451,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:15::1 ++ via: 3001:67c:1562:1 + """ + ), + "expected_eni": textwrap.dedent( +@@ -3044,11 +2514,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:15::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:1562:1 metric 10000 \ + || true +- pre-down route del -A inet6 3001:67c:1/32 gw 3001:67c:15::1 metric 10000 \ ++ pre-down route del -A inet6 3001:67c:1/32 gw 3001:67c:1562:1 metric 10000 \ + || true + """ + ), +@@ -3091,8 +2561,8 @@ iface bond0 inet6 static + - to: 2001:67c:1562:8007::1/64 + via: 2001:67c:1562:8007::aac:40b2 + - metric: 10000 +- to: 3001:67c:15:8007::1/64 +- via: 3001:67c:15:8007::aac:40b2 ++ to: 3001:67c:1562:8007::1/64 ++ via: 3001:67c:1562:8007::aac:40b2 + """ + ), + "expected_netplan-v2": textwrap.dedent( +@@ -3124,8 +2594,8 @@ iface bond0 inet6 static + - to: 2001:67c:1562:8007::1/64 + via: 2001:67c:1562:8007::aac:40b2 + - metric: 10000 +- to: 3001:67c:15:8007::1/64 +- via: 3001:67c:15:8007::aac:40b2 ++ to: 3001:67c:1562:8007::1/64 ++ via: 3001:67c:1562:8007::aac:40b2 + ethernets: + eth0: + match: +@@ -3224,8 +2694,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:15::1 metric 10000 dev bond0 ++ 2001:67c:1/32 via 2001:67c:1562:1 dev bond0 ++ 3001:67c:1/32 via 3001:67c:1562:1 metric 10000 dev bond0 + """ + ), + "route-bond0": textwrap.dedent( +@@ -3248,88 +2718,6 @@ 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( +@@ -3413,58 +2801,6 @@ 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( +@@ -3573,82 +2909,6 @@ 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( +@@ -3777,92 +3037,25 @@ 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 ++ }, ++} + +- [user] +- org.freedesktop.NetworkManager.origin=cloud-init + +- [ethernet] +- mac-address=52:54:00:12:34:00 +- +- [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_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 = { +@@ -4304,6 +3497,7 @@ 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, " +@@ -4878,6 +4072,78 @@ 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) +@@ -5433,281 +4699,6 @@ 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") +@@ -7145,9 +6136,9 @@ class TestNetworkdRoundTrip(CiTestCase): + + class TestRenderersSelect: + @pytest.mark.parametrize( +- "renderer_selected,netplan,eni,sys,network_manager,networkd", ++ "renderer_selected,netplan,eni,nm,scfg,sys,networkd", + ( +- # -netplan -ifupdown -sys -network-manager -networkd raises error ++ # -netplan -ifupdown -nm -scfg -sys raises error + ( + net.RendererNotFoundError, + False, +@@ -7155,51 +6146,52 @@ class TestRenderersSelect: + False, + False, + False, ++ False, + ), +- # -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), ++ # -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), + ), + ) + @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( +@@ -7257,7 +6249,7 @@ class TestNetRenderers(CiTestCase): + priority=["sysconfig", "eni"], + ) + +- @mock.patch("cloudinit.net.sysconfig.available") ++ @mock.patch("cloudinit.net.sysconfig.available_sysconfig") + @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 4525c49c..3c29e2f7 100644 +--- a/tests/unittests/test_net_activators.py ++++ b/tests/unittests/test_net_activators.py +@@ -41,20 +41,18 @@ NETPLAN_CALL_LIST = [ + + @pytest.fixture + def available_mocks(): +- mocks = namedtuple("Mocks", "m_which, m_file, m_exists") ++ mocks = namedtuple("Mocks", "m_which, m_file") + with patch("cloudinit.subp.which", return_value=True) as m_which: + with patch("os.path.isfile", return_value=True) as m_file: +- with patch("os.path.exists", return_value=True) as m_exists: +- yield mocks(m_which, m_file, m_exists) ++ yield mocks(m_which, m_file) + + + @pytest.fixture + def unavailable_mocks(): +- mocks = namedtuple("Mocks", "m_which, m_file, m_exists") ++ mocks = namedtuple("Mocks", "m_which, m_file") + with patch("cloudinit.subp.which", return_value=False) as m_which: + with patch("os.path.isfile", return_value=False) as m_file: +- with patch("os.path.exists", return_value=False) as m_exists: +- yield mocks(m_which, m_file, m_exists) ++ yield mocks(m_which, m_file) + + + class TestSearchAndSelect: +@@ -115,6 +113,10 @@ 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}), +@@ -126,6 +128,7 @@ NETWORKD_AVAILABLE_CALLS = [ + [ + (IfUpDownActivator, IF_UP_DOWN_AVAILABLE_CALLS), + (NetplanActivator, NETPLAN_AVAILABLE_CALLS), ++ (NetworkManagerActivator, NETWORK_MANAGER_AVAILABLE_CALLS), + (NetworkdActivator, NETWORKD_AVAILABLE_CALLS), + ], + ) +@@ -141,72 +144,8 @@ IF_UP_DOWN_BRING_UP_CALL_LIST = [ + ] + + NETWORK_MANAGER_BRING_UP_CALL_LIST = [ +- ( +- ( +- [ +- "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", +- ] +- ), +- ], +- ), +- {}, +- ), ++ ((["nmcli", "connection", "up", "ifname", "eth0"],), {}), ++ ((["nmcli", "connection", "up", "ifname", "eth1"],), {}), + ] + + NETWORKD_BRING_UP_CALL_LIST = [ +@@ -230,11 +169,9 @@ class TestActivatorsBringUp: + def test_bring_up_interface( + self, m_subp, activator, expected_call_list, available_mocks + ): +- index = 0 + activator.bring_up_interface("eth0") +- for call in m_subp.call_args_list: +- assert call == expected_call_list[index] +- index += 1 ++ assert len(m_subp.call_args_list) == 1 ++ assert m_subp.call_args_list[0] == expected_call_list[0] + + @patch("cloudinit.subp.subp", return_value=("", "")) + def test_bring_up_interfaces( +@@ -271,8 +208,8 @@ IF_UP_DOWN_BRING_DOWN_CALL_LIST = [ + ] + + NETWORK_MANAGER_BRING_DOWN_CALL_LIST = [ +- ((["nmcli", "device", "disconnect", "eth0"],), {}), +- ((["nmcli", "device", "disconnect", "eth1"],), {}), ++ ((["nmcli", "connection", "down", "eth0"],), {}), ++ ((["nmcli", "connection", "down", "eth1"],), {}), + ] + + NETWORKD_BRING_DOWN_CALL_LIST = [ +-- +2.27.0 + diff --git a/SOURCES/ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch b/SOURCES/ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch new file mode 100644 index 0000000..6532fab --- /dev/null +++ b/SOURCES/ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch @@ -0,0 +1,75 @@ +From 02e7b89c157f8c3243f0d91cf5652cf27db44b72 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Mon, 8 Aug 2022 10:10:26 +0200 +Subject: [PATCH 2/2] Revert "Use Network-Manager and Netplan as default + renderers for RHEL and Fedora (#1465)" + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 81: Revert "Use Network-Manager and Netplan as default renderers for RHEL and Fedora (#1465)" +RH-Commit: [2/2] 746b2e33356376e250b799261031676174e8ccc9 +RH-Bugzilla: 2107464 2110066 2117526 2104393 2098624 +RH-Acked-by: Eduardo Otubo +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohamed Gamal Morsy + +As NM is reverted, remove also documentation and any trace of it. +This reverts commit 13ded463a6a0b1b0bf0dffc0a997f006dd25c4f3. + +Signed-off-by: Emanuele Giuseppe Esposito +--- + config/cloud.cfg.tmpl | 3 --- + doc/rtd/topics/network-config.rst | 12 +----------- + 2 files changed, 1 insertion(+), 14 deletions(-) + +diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl +index f4d2fd14..80ab4f96 100644 +--- a/config/cloud.cfg.tmpl ++++ b/config/cloud.cfg.tmpl +@@ -353,7 +353,4 @@ 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 f503caab..c461a3fe 100644 +--- a/doc/rtd/topics/network-config.rst ++++ b/doc/rtd/topics/network-config.rst +@@ -188,15 +188,6 @@ 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 `_ 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 +@@ -224,7 +215,6 @@ 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 +@@ -233,7 +223,7 @@ supplying an updated configuration in cloud-config. :: + + system_info: + network: +- renderers: ['netplan', 'network-manager', 'eni', 'sysconfig', 'freebsd', 'netbsd', 'openbsd'] ++ renderers: ['netplan', 'eni', 'sysconfig', 'freebsd', 'netbsd', 'openbsd'] + + + Network Configuration Tools +-- +2.27.0 + 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 +Date: Mon, 30 May 2022 16:46:41 +0200 +Subject: [PATCH] Support EC2 tags in instance metadata (#1309) + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 70: Support EC2 tags in instance metadata (#1309) +RH-Commit: [1/1] 2497547016173a4c6e7d3c900f80de390d445c44 +RH-Bugzilla: 2082686 +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohamed Gamal Morsy + +commit 40c52ce1f4049449b04f93226721f63af874c5c7 +Author: Eduardo Dobay +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 +--- + 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 //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 +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 +RH-MergeRequest: 57: Add native NetworkManager support (#1224) +RH-Commit: [2/2] f2f977564bea496b0d76c0cef242959d03c2c73e +RH-Bugzilla: 2059872 +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Jon Maloy +RH-Acked-by: Eduardo Otubo + +commit 7703aa98b89c8daba207c28a0422268ead10019a +Author: Emanuele Giuseppe Esposito +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 + + Signed-off-by: Emanuele Giuseppe Esposito + +Signed-off-by: Emanuele Giuseppe Esposito +--- + 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 `_ 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/SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch b/SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch new file mode 100644 index 0000000..69ec964 --- /dev/null +++ b/SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch @@ -0,0 +1,84 @@ +From ddfd2eba79b4849309c37472dfb5852811b03391 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Thu, 19 Jan 2023 09:46:10 +0100 +Subject: [PATCH] cc_set_hostname: ignore /var/lib/cloud/data/set-hostname if + it's empty (#1967) + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 88: cc_set_hostname: ignore /var/lib/cloud/data/set-hostname if it's empty (#1967) +RH-Bugzilla: 2162258 +RH-Acked-by: Mohamed Gamal Morsy +RH-Acked-by: Jon Maloy +RH-Commit: [1/1] 04aaaf46290c4488dd46c9c2673b0bf038b7d311 + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2162258 + +commit 9c7502a801763520639c66125eb373123d1e4f44 +Author: Emanuele Giuseppe Esposito +Date: Wed Jan 18 17:55:16 2023 +0100 + + cc_set_hostname: ignore /var/lib/cloud/data/set-hostname if it's empty (#1967) + + If the file exists but is empty, do nothing. + Otherwise cloud-init will crash because it does not handle the empty file. + + RHBZ: 2140893 + + Signed-off-by: Emanuele Giuseppe Esposito + +Signed-off-by: Emanuele Giuseppe Esposito +--- + cloudinit/config/cc_set_hostname.py | 2 +- + tests/unittests/config/test_cc_set_hostname.py | 17 +++++++++++++++++ + 2 files changed, 18 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/config/cc_set_hostname.py b/cloudinit/config/cc_set_hostname.py +index eb0ca328..9d78f6ad 100644 +--- a/cloudinit/config/cc_set_hostname.py ++++ b/cloudinit/config/cc_set_hostname.py +@@ -86,7 +86,7 @@ def handle(name, cfg, cloud, log, _args): + # distro._read_hostname implementation so we only validate one artifact. + prev_fn = os.path.join(cloud.get_cpath("data"), "set-hostname") + prev_hostname = {} +- if os.path.exists(prev_fn): ++ if os.path.exists(prev_fn) and os.stat(prev_fn).st_size > 0: + prev_hostname = util.load_json(util.load_file(prev_fn)) + hostname_changed = hostname != prev_hostname.get( + "hostname" +diff --git a/tests/unittests/config/test_cc_set_hostname.py b/tests/unittests/config/test_cc_set_hostname.py +index fd994c4e..a819039b 100644 +--- a/tests/unittests/config/test_cc_set_hostname.py ++++ b/tests/unittests/config/test_cc_set_hostname.py +@@ -5,6 +5,7 @@ import os + import shutil + import tempfile + from io import BytesIO ++from pathlib import Path + from unittest import mock + + from configobj import ConfigObj +@@ -204,5 +205,21 @@ class TestHostname(t_help.FilesystemMockingTestCase): + str(ctx_mgr.exception), + ) + ++ def test_ignore_empty_previous_artifact_file(self): ++ cfg = { ++ "hostname": "blah", ++ "fqdn": "blah.blah.blah.yahoo.com", ++ } ++ distro = self._fetch_distro("debian") ++ paths = helpers.Paths({"cloud_dir": self.tmp}) ++ ds = None ++ cc = cloud.Cloud(ds, paths, {}, distro, None) ++ self.patchUtils(self.tmp) ++ prev_fn = Path(cc.get_cpath("data")) / "set-hostname" ++ prev_fn.touch() ++ cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) ++ contents = util.load_file("/etc/hostname") ++ self.assertEqual("blah", contents.strip()) ++ + + # vi: ts=4 expandtab +-- +2.39.1 + diff --git a/SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch b/SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch new file mode 100644 index 0000000..8949652 --- /dev/null +++ b/SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch @@ -0,0 +1,146 @@ +From 528136e7f6c307f035f8db0f14313a213697d2d0 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Thu, 8 Sep 2022 17:42:26 +0200 +Subject: [PATCH] cloud.cfg.tmpl: make sure "centos" settings are identical to + "rhel" (#1639) + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 83: cloud.cfg.tmpl: make sure "centos" settings are identical to "rhel" (#1639) +RH-Bugzilla: 2115576 +RH-Acked-by: Camilla Conte +RH-Acked-by: Jon Maloy +RH-Commit: [1/1] f503ce4f79b7d783cd0a4e1ed0977e63a4715031 + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2115576 + +commit 7593243a1abe2ccaf4698579720999380a4da73b +Author: Emanuele Giuseppe Esposito +Date: Wed Sep 7 14:53:26 2022 +0200 + + cloud.cfg.tmpl: make sure "centos" settings are identical to "rhel" (#1639) + + We have a couple of bugs where centos does not have the default user as rhel. + This PR makes sure the configuration is exactly the same. + + Signed-off-by: Emanuele Giuseppe Esposito + + RHBZ: 2115565 + RHBZ: 2115576 + Conflicts: + config/cloud.cfg.tmpl: "openmandriva" distro added in the options + +Signed-off-by: Emanuele Giuseppe Esposito +--- + config/cloud.cfg.tmpl | 27 +++++++++++++------------ + tests/unittests/test_render_cloudcfg.py | 1 + + 2 files changed, 15 insertions(+), 13 deletions(-) + +diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl +index 80ab4f96..08b6efbc 100644 +--- a/config/cloud.cfg.tmpl ++++ b/config/cloud.cfg.tmpl +@@ -2,6 +2,7 @@ + # The top level settings are used as module + # and system configuration. + {% set is_bsd = variant in ["dragonfly", "freebsd", "netbsd", "openbsd"] %} ++{% set is_rhel = variant in ["rhel", "centos"] %} + {% if is_bsd %} + syslog_fix_perms: root:wheel + {% elif variant in ["suse"] %} +@@ -32,9 +33,9 @@ disable_root: false + disable_root: true + {% endif %} + +-{% if variant in ["almalinux", "alpine", "amazon", "centos", "cloudlinux", "eurolinux", +- "fedora", "miraclelinux", "openEuler", "rhel", "rocky", "virtuozzo"] %} +-{% if variant == "rhel" %} ++{% if variant in ["almalinux", "alpine", "amazon", "cloudlinux", "eurolinux", ++ "fedora", "miraclelinux", "openEuler", "openmandriva", "rocky", "virtuozzo"] or is_rhel %} ++{% if is_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'] +@@ -70,7 +71,7 @@ network: + config: disabled + {% endif %} + +-{% if variant == "rhel" %} ++{% if is_rhel %} + # Default redhat settings: + ssh_deletekeys: true + ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519'] +@@ -119,16 +120,16 @@ cloud_config_modules: + {% endif %} + {% if variant not in ["photon"] %} + - ssh-import-id +-{% if variant not in ["rhel"] %} ++{% if not is_rhel %} + - keyboard + {% endif %} + - locale + {% endif %} + - set-passwords +-{% if variant in ["rhel"] %} ++{% if is_rhel %} + - rh_subscription + {% endif %} +-{% if variant in ["rhel", "fedora", "photon"] %} ++{% if variant in ["fedora", "openmandriva", "photon"] or is_rhel %} + {% if variant not in ["photon"] %} + - spacewalk + {% endif %} +@@ -193,9 +194,9 @@ cloud_final_modules: + # (not accessible to handlers/transforms) + system_info: + # This will affect which distro class gets used +-{% if variant in ["almalinux", "alpine", "amazon", "arch", "centos", "cloudlinux", "debian", ++{% if variant in ["almalinux", "alpine", "amazon", "arch", "cloudlinux", "debian", + "eurolinux", "fedora", "freebsd", "gentoo", "netbsd", "miraclelinux", "openbsd", "openEuler", +- "photon", "rhel", "rocky", "suse", "ubuntu", "virtuozzo"] %} ++ "openmandriva", "photon", "rocky", "suse", "ubuntu", "virtuozzo"] or is_rhel %} + distro: {{ variant }} + {% elif variant in ["dragonfly"] %} + distro: dragonflybsd +@@ -248,15 +249,15 @@ system_info: + primary: http://ports.ubuntu.com/ubuntu-ports + security: http://ports.ubuntu.com/ubuntu-ports + ssh_svcname: ssh +-{% elif variant in ["almalinux", "alpine", "amazon", "arch", "centos", "cloudlinux", "eurolinux", +- "fedora", "gentoo", "miraclelinux", "openEuler", "rhel", "rocky", "suse", "virtuozzo"] %} ++{% elif variant in ["almalinux", "alpine", "amazon", "arch", "cloudlinux", "eurolinux", ++ "fedora", "gentoo", "miraclelinux", "openEuler", "openmandriva", "rocky", "suse", "virtuozzo"] or is_rhel %} + # Default user name + that default users groups (if added/used) + default_user: + {% if variant == "amazon" %} + name: ec2-user + lock_passwd: True + gecos: EC2 Default User +-{% elif variant == "rhel" %} ++{% elif is_rhel %} + name: cloud-user + lock_passwd: true + gecos: Cloud User +@@ -275,7 +276,7 @@ system_info: + groups: [adm, sudo] + {% elif variant == "arch" %} + groups: [wheel, users] +-{% elif variant == "rhel" %} ++{% elif is_rhel %} + groups: [adm, systemd-journal] + {% else %} + groups: [wheel, adm, systemd-journal] +diff --git a/tests/unittests/test_render_cloudcfg.py b/tests/unittests/test_render_cloudcfg.py +index 9f95d448..1a6e2715 100644 +--- a/tests/unittests/test_render_cloudcfg.py ++++ b/tests/unittests/test_render_cloudcfg.py +@@ -69,6 +69,7 @@ class TestRenderCloudCfg: + "amazon": "ec2-user", + "debian": "ubuntu", + "rhel": "cloud-user", ++ "centos": "cloud-user", + "unknown": "ubuntu", + } + default_user = system_cfg["system_info"]["default_user"]["name"] +-- +2.37.3 + diff --git a/SOURCES/ci-setup.py-adjust-udev-rules-default-path-1513.patch b/SOURCES/ci-setup.py-adjust-udev-rules-default-path-1513.patch new file mode 100644 index 0000000..1385aae --- /dev/null +++ b/SOURCES/ci-setup.py-adjust-udev-rules-default-path-1513.patch @@ -0,0 +1,57 @@ +From ed7060ac1d5003f70fc3da4d6006a1a958a47b04 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Mon, 20 Jun 2022 10:31:14 +0200 +Subject: [PATCH 2/2] setup.py: adjust udev/rules default path (#1513) + +RH-Author: Emanuele Giuseppe Esposito +RH-MergeRequest: 80: setup.py: adjust udev/rules default path (#1513) +RH-Commit: [2/2] 2cb64b004acbe1b6a30f943b0da51d2d1f2f0d50 (eesposit/cloud-init) +RH-Bugzilla: 2096269 +RH-Acked-by: Vitaly Kuznetsov +RH-Acked-by: Mohamed Gamal Morsy + +commit 70715125f3af118ae242770e61064c24f41e9a02 +Author: Emanuele Giuseppe Esposito +Date: Thu Jun 16 20:39:42 2022 +0200 + + setup.py: adjust udev/rules default path (#1513) + + RHEL must put cloudinit .rules files in /usr/lib/udev/rules.d + This place is a rhel standard and since it is used by all packages + cannot be modified. + + Signed-off-by: Emanuele Giuseppe Esposito + +Signed-off-by: Emanuele Giuseppe Esposito +--- + setup.py | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/setup.py b/setup.py +index a9132d2c..fdf27cd7 100755 +--- a/setup.py ++++ b/setup.py +@@ -302,6 +302,11 @@ data_files = [ + ), + ] + if not platform.system().endswith("BSD"): ++ ++ RULES_PATH = LIB ++ if os.path.isfile("/etc/redhat-release"): ++ RULES_PATH = "/usr/lib" ++ + data_files.extend( + [ + ( +@@ -309,7 +314,7 @@ if not platform.system().endswith("BSD"): + ["tools/hook-network-manager"], + ), + (ETC + "/dhcp/dhclient-exit-hooks.d/", ["tools/hook-dhclient"]), +- (LIB + "/udev/rules.d", [f for f in glob("udev/*.rules")]), ++ (RULES_PATH + "/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"], +-- +2.31.1 + diff --git a/SOURCES/cloud-init-tmpfiles.conf b/SOURCES/cloud-init-tmpfiles.conf new file mode 100644 index 0000000..0c6d2a3 --- /dev/null +++ b/SOURCES/cloud-init-tmpfiles.conf @@ -0,0 +1 @@ +d /run/cloud-init 0700 root root - - diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec new file mode 100644 index 0000000..2658782 --- /dev/null +++ b/SPECS/cloud-init.spec @@ -0,0 +1,773 @@ +%{!?license: %global license %%doc} + +# The only reason we are archful is because dmidecode is ExclusiveArch +# https://bugzilla.redhat.com/show_bug.cgi?id=1067089 +%global debug_package %{nil} + +Name: cloud-init +Version: 22.1 +Release: 8%{?dist} +Summary: Cloud instance init scripts + +Group: System Environment/Base +License: GPLv3 +URL: http://launchpad.net/cloud-init +Source0: https://launchpad.net/cloud-init/trunk/%{version}/+download/%{name}-%{version}.tar.gz +Source1: cloud-init-tmpfiles.conf + +Patch0001: 0001-Add-initial-redhat-setup.patch +Patch0002: 0002-Do-not-write-NM_CONTROLLED-no-in-generated-interface.patch +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 +# For bz#2096269 - Adjust udev/rules default path[RHEL-8] +Patch12: ci-setup.py-adjust-udev-rules-default-path-1513.patch +# For bz#2107464 - [RHEL-8.7] Cannot run sysconfig when changing the priority of network renderers +# For bz#2110066 - DNS integration with OpenStack/cloud-init/NetworkManager is not working +# For bz#2117526 - [RHEL8.7] Revert patch of configuring networking by NM keyfiles +# For bz#2104393 - [RHEL-8.7]Failed to config static IP and IPv6 according to VMware Customization Config File +# For bz#2098624 - [RHEL-8.7] IPv6 not workable when cloud-init configure network using NM keyfiles +Patch13: ci-Revert-Add-native-NetworkManager-support-1224.patch +# For bz#2107464 - [RHEL-8.7] Cannot run sysconfig when changing the priority of network renderers +# For bz#2110066 - DNS integration with OpenStack/cloud-init/NetworkManager is not working +# For bz#2117526 - [RHEL8.7] Revert patch of configuring networking by NM keyfiles +# For bz#2104393 - [RHEL-8.7]Failed to config static IP and IPv6 according to VMware Customization Config File +# For bz#2098624 - [RHEL-8.7] IPv6 not workable when cloud-init configure network using NM keyfiles +Patch14: ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch +# For bz#2115576 - cloud-init configures user "centos" or "rhel" instead of "cloud-user" with cloud-init-22.1 +Patch15: ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch +# For bz#2151861 - [RHEL-8] Ensure network ready before cloud-init service runs on RHEL +Patch16: ci-Ensure-network-ready-before-cloud-init-service-runs-.patch +# For bz#2162258 - systemd[1]: Failed to start Initial cloud-init job after reboot system via sysrq 'b' [RHEL-8] +Patch17: ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch + +BuildArch: noarch + +BuildRequires: pkgconfig(systemd) +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: systemd + +# For tests +BuildRequires: iproute +BuildRequires: python3-configobj +# # https://bugzilla.redhat.com/show_bug.cgi?id=1417029 +BuildRequires: python3-httpretty >= 0.8.14-2 +BuildRequires: python3-jinja2 +BuildRequires: python3-jsonpatch +BuildRequires: python3-jsonschema +BuildRequires: python3-mock +BuildRequires: python3-nose +BuildRequires: python3-oauthlib +BuildRequires: python3-prettytable +BuildRequires: python3-pyserial +BuildRequires: python3-PyYAML +BuildRequires: python3-requests +BuildRequires: python3-six +BuildRequires: python3-unittest2 +# dnf is needed to make cc_ntp unit tests work +# https://bugs.launchpad.net/cloud-init/+bug/1721573 +BuildRequires: /usr/bin/dnf + +Requires: e2fsprogs +Requires: iproute +Requires: libselinux-python3 +Requires: policycoreutils-python3 +Requires: procps +Requires: python3-configobj +Requires: python3-jinja2 +Requires: python3-jsonpatch +Requires: python3-jsonschema +Requires: python3-oauthlib +Requires: python3-prettytable +Requires: python3-pyserial +Requires: python3-PyYAML +Requires: python3-requests +Requires: python3-six +Requires: shadow-utils +Requires: util-linux +Requires: xfsprogs +Requires: dhcp-client +# https://bugzilla.redhat.com/show_bug.cgi?id=2039697 +Requires: gdisk +Requires: openssl +Requires: python3-netifaces + +%{?systemd_requires} + +%description +Cloud-init is a set of init scripts for cloud instances. Cloud instances +need special scripts to run during initialization to retrieve and install +ssh keys and to let the user run various scripts. + + +%prep +%autosetup -p1 + +# Change shebangs +sed -i -e 's|#!/usr/bin/env python|#!/usr/bin/env python3|' \ + -e 's|#!/usr/bin/python|#!/usr/bin/python3|' tools/* cloudinit/ssh_util.py + +%build +%py3_build + + +%install +%py3_install -- + +sed -i "s,@@PACKAGED_VERSION@@,%{version}-%{release}," $RPM_BUILD_ROOT/%{python3_sitelib}/cloudinit/version.py + +mkdir -p $RPM_BUILD_ROOT/var/lib/cloud + +# /run/cloud-init needs a tmpfiles.d entry +mkdir -p $RPM_BUILD_ROOT/run/cloud-init +mkdir -p $RPM_BUILD_ROOT/%{_tmpfilesdir} +cp -p %{SOURCE1} $RPM_BUILD_ROOT/%{_tmpfilesdir}/%{name}.conf + +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/rsyslog.d +cp -p tools/21-cloudinit.conf $RPM_BUILD_ROOT/%{_sysconfdir}/rsyslog.d/21-cloudinit.conf + +# Make installed NetworkManager hook name less generic +mv $RPM_BUILD_ROOT/etc/NetworkManager/dispatcher.d/hook-network-manager \ + $RPM_BUILD_ROOT/etc/NetworkManager/dispatcher.d/cloud-init-azure-hook + +[ ! -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 + +# installing man pages +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man1/ +for man in cloud-id.1 cloud-init.1 cloud-init-per.1; do + install -c -m 0644 doc/man/${man} ${RPM_BUILD_ROOT}%{_mandir}/man1/${man} + chmod -x ${RPM_BUILD_ROOT}%{_mandir}/man1/* +done + +%clean +rm -rf $RPM_BUILD_ROOT + + +%post +if [ $1 -eq 1 ] ; then + # Initial installation + # Enabled by default per "runs once then goes away" exception + /bin/systemctl enable cloud-config.service >/dev/null 2>&1 || : + /bin/systemctl enable cloud-final.service >/dev/null 2>&1 || : + /bin/systemctl enable cloud-init.service >/dev/null 2>&1 || : + /bin/systemctl enable cloud-init-local.service >/dev/null 2>&1 || : + /bin/systemctl enable cloud-init.target >/dev/null 2>&1 || : +elif [ $1 -eq 2 ]; then + # Upgrade. If the upgrade is from a version older than 0.7.9-8, + # there will be stale systemd config + /bin/systemctl is-enabled cloud-config.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-config.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-final.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-final.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-init.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-init.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-init-local.service >/dev/null 2>&1 && + /bin/systemctl reenable cloud-init-local.service >/dev/null 2>&1 || : + + /bin/systemctl is-enabled cloud-init.target >/dev/null 2>&1 && + /bin/systemctl reenable cloud-init.target >/dev/null 2>&1 || : +fi + +%preun +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + /bin/systemctl --no-reload disable cloud-config.service >/dev/null 2>&1 || : + /bin/systemctl --no-reload disable cloud-final.service >/dev/null 2>&1 || : + /bin/systemctl --no-reload disable cloud-init.service >/dev/null 2>&1 || : + /bin/systemctl --no-reload disable cloud-init-local.service >/dev/null 2>&1 || : + /bin/systemctl --no-reload disable cloud-init.target >/dev/null 2>&1 || : + # One-shot services -> no need to stop +fi + +%postun +%systemd_postun cloud-config.service cloud-config.target cloud-final.service cloud-init.service cloud-init.target cloud-init-local.service + +%files +%license LICENSE +%config(noreplace) %{_sysconfdir}/cloud/cloud.cfg +%dir %{_sysconfdir}/cloud/cloud.cfg.d +%config(noreplace) %{_sysconfdir}/cloud/cloud.cfg.d/*.cfg +%doc %{_sysconfdir}/cloud/cloud.cfg.d/README +%dir %{_sysconfdir}/cloud/templates +%config(noreplace) %{_sysconfdir}/cloud/templates/* +%{_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 +%{_tmpfilesdir}/%{name}.conf +%{python3_sitelib}/* +%{_libexecdir}/%{name} +%{_bindir}/cloud-init* +%doc %{_datadir}/doc/%{name} +%{_mandir}/man1/* +%dir %verify(not mode) /run/cloud-init +%dir /var/lib/cloud +/etc/NetworkManager/dispatcher.d/cloud-init-azure-hook +/etc/dhcp/dhclient-exit-hooks.d/hook-dhclient +%{_udevrulesdir}/66-azure-ephemeral.rules +%{_datadir}/bash-completion/completions/cloud-init +%{_bindir}/cloud-id +/usr/lib/systemd/system-generators/cloud-init-generator +%{_sysconfdir}/systemd/system/sshd-keygen@.service.d/disable-sshd-keygen-if-cloud-init-active.conf + + +%dir %{_sysconfdir}/rsyslog.d +%config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf + +%changelog +* Mon Jan 30 2023 Camilla Conte - 22.1-8 +- ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch [bz#2162258] +- Resolves: bz#2162258 + (systemd[1]: Failed to start Initial cloud-init job after reboot system via sysrq 'b' [RHEL-8]) + +* Wed Dec 28 2022 Camilla Conte - 22.1-7 +- ci-Ensure-network-ready-before-cloud-init-service-runs-.patch [bz#2151861] +- Resolves: bz#2151861 + ([RHEL-8] Ensure network ready before cloud-init service runs on RHEL) + +* Mon Oct 17 2022 Jon Maloy - 22.1-6 +- ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch [bz#2115576] +- Resolves: bz#2115576 + (cloud-init configures user "centos" or "rhel" instead of "cloud-user" with cloud-init-22.1) + +* Wed Aug 17 2022 Jon Maloy - 22.1-5 +- ci-Revert-Add-native-NetworkManager-support-1224.patch [bz#2107464 bz#2110066 bz#2117526 bz#2104393 bz#2098624] +- ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch [bz#2107464 bz#2110066 bz#2117526 bz#2104393 bz#2098624] +- Resolves: bz#2107464 + ([RHEL-8.7] Cannot run sysconfig when changing the priority of network renderers) +- Resolves: bz#2110066 + (DNS integration with OpenStack/cloud-init/NetworkManager is not working) +- Resolves: bz#2117526 + ([RHEL8.7] Revert patch of configuring networking by NM keyfiles) +- Resolves: bz#2104393 + ([RHEL-8.7]Failed to config static IP and IPv6 according to VMware Customization Config File) +- Resolves: bz#2098624 + ([RHEL-8.7] IPv6 not workable when cloud-init configure network using NM keyfiles) + +* Tue Jul 12 2022 Miroslav Rezanina - 22.1-4 +- ci-cloud-init.spec-adjust-path-for-66-azure-ephemeral.r.patch [bz#2096269] +- ci-setup.py-adjust-udev-rules-default-path-1513.patch [bz#2096269] +- Resolves: bz#2096269 + (Adjust udev/rules default path[RHEL-8]) + +* Thu Jun 23 2022 Jon Maloy - 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 - 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 - 22.1-1 +- Rebaes to 22.1 [bz#2065544] +- Resolves: bz#2065544 + ([RHEL-8.7.0] cloud-init rebase to 22.1) + +* Fri Apr 01 2022 Camilla Conte - 21.1-15 +- ci-Detect-a-Python-version-change-and-clear-the-cache-8.patch [bz#1935826] +- ci-Fix-MIME-policy-failure-on-python-version-upgrade-93.patch [bz#1935826] + +* Fri Feb 25 2022 Jon Maloy - 21.1-14 +- ci-Fix-IPv6-netmask-format-for-sysconfig-1215.patch [bz#2046540] +- Resolves: bz#2046540 + (cloud-init writes route6-$DEVICE config with a HEX netmask. ip route does not like : Error: inet6 prefix is expected rather than "fd00:fd00:fd00::/ffff:ffff:ffff:ffff::".) + +* Tue Jan 25 2022 Jon Maloy - 21.1-13 +- ci-Add-flexibility-to-IMDS-api-version-793.patch [bz#2023940] +- ci-Azure-helper-Ensure-Azure-http-handler-sleeps-betwee.patch [bz#2023940] +- ci-azure-Removing-ability-to-invoke-walinuxagent-799.patch [bz#2023940] +- ci-Azure-eject-the-provisioning-iso-before-reporting-re.patch [bz#2023940] +- ci-Azure-Retrieve-username-and-hostname-from-IMDS-865.patch [bz#2023940] +- ci-Azure-Retry-net-metadata-during-nic-attach-for-non-t.patch [bz#2023940] +- ci-Azure-adding-support-for-consuming-userdata-from-IMD.patch [bz#2023940] +- Resolves: bz#2023940 + ([RHEL-8] Support for provisioning Azure VM with userdata) + +* Wed Jan 19 2022 Jon Maloy - 21.1-12 +- ci-Add-gdisk-and-openssl-as-deps-to-fix-UEFI-Azure-init.patch [bz#2039697] +- ci-Datasource-for-VMware-953.patch [bz#2026587] +- ci-Change-netifaces-dependency-to-0.10.4-965.patch [bz#2026587] +- ci-Update-dscheck_VMware-s-rpctool-check-970.patch [bz#2026587] +- ci-Revert-unnecesary-lcase-in-ds-identify-978.patch [bz#2026587] +- ci-Add-netifaces-package-as-a-Requires-in-cloud-init.sp.patch [bz#2026587] +- Resolves: bz#2039697 + ([RHEL8] [Azure] cloud-init fails to configure the system) +- Resolves: bz#2026587 + ([cloud-init][RHEL8] Support for cloud-init datasource 'cloud-init-vmware-guestinfo') + +* Wed Dec 08 2021 Jon Maloy - 21.1-11 +- ci-cloudinit-net-handle-two-different-routes-for-the-sa.patch [bz#2028028] +- Resolves: bz#2028028 + ([RHEL-8] Above 19.2 of cloud-init fails to configure routes when configuring static and default routes to the same destination IP) + +* Mon Dec 06 2021 Jon Maloy - 21.1-10 +- ci-fix-error-on-upgrade-caused-by-new-vendordata2-attri.patch [bz#2021538] +- Resolves: bz#2021538 + (cloud-init.service fails to start after package update) + +* Mon Oct 25 2021 Jon Maloy - 21.1-9 +- ci-cc_ssh.py-fix-private-key-group-owner-and-permission.patch [bz#2013644] +- Resolves: bz#2013644 + (cloud-init fails to set host key permissions correctly) + +* Thu Sep 23 2021 Miroslav Rezanina - 21.1-8 +- ci-ssh_utils.py-ignore-when-sshd_config-options-are-not.patch [bz#1862933] +- Resolves: bz#1862933 + (cloud-init fails with ValueError: need more than 1 value to unpack[rhel-8]) + +* Fri Aug 27 2021 Miroslav Rezanina - 21.1-7 +- ci-Fix-home-permissions-modified-by-ssh-module-SC-338-9.patch [bz#1995840] +- Resolves: bz#1995840 + ([cloudinit] Fix home permissions modified by ssh module) + +* Wed Aug 11 2021 Miroslav Rezanina - 21.1-6 +- ci-Stop-copying-ssh-system-keys-and-check-folder-permis.patch [bz#1862967] +- Resolves: bz#1862967 + ([cloud-init]Customize ssh AuthorizedKeysFile causes login failure) + +* Fri Aug 06 2021 Miroslav Rezanina - 21.1-5 +- ci-Add-dhcp-client-as-a-dependency.patch [bz#1977385] +- Resolves: bz#1977385 + ([Azure][RHEL-8] cloud-init must require dhcp-client on Azure) + +* Mon Jul 19 2021 Miroslav Rezanina - 21.1-4 +- ci-ssh-util-allow-cloudinit-to-merge-all-ssh-keys-into-.patch [bz#1862967] +- Resolves: bz#1862967 + ([cloud-init]Customize ssh AuthorizedKeysFile causes login failure) + +* Mon Jul 12 2021 Miroslav Rezanina - 21.1-3 +- ci-write-passwords-only-to-serial-console-lock-down-clo.patch [bz#1945891] +- Resolves: bz#1945891 + (CVE-2021-3429 cloud-init: randomly generated passwords logged in clear-text to world-readable file [rhel-8]) + +* Fri Jun 11 2021 Miroslav Rezanina - 21.1-2 +- ci-rhel-cloud.cfg-remove-ssh_genkeytypes-in-settings.py.patch [bz#1957532] +- ci-cloud-init.spec.template-update-systemd_postun-param.patch [bz#1952089] +- Resolves: bz#1957532 + ([cloud-init] From RHEL 82+ cloud-init no longer displays sshd keys fingerprints from instance launched from a backup image) +- Resolves: bz#1952089 + (cloud-init brew build fails on Fedora 33) + +* Thu May 27 2021 Miroslav Rezanina - 21.1-1.el8 +- Rebaes to 21.1 [bz#1958174] +- Resolves: bz#1958174 + ([RHEL-8.5.0] Rebase cloud-init to 21.1) + +* Thu May 13 2021 Miroslav Rezanina - 20.3-10.el8_4.3 +- ci-get_interfaces-don-t-exclude-Open-vSwitch-bridge-bon.patch [bz#1957135] +- ci-net-exclude-OVS-internal-interfaces-in-get_interface.patch [bz#1957135] +- Resolves: bz#1957135 + (Intermittent failure to start cloud-init due to failure to detect macs [rhel-8.4.0.z]) + +* Tue Apr 06 2021 Miroslav Rezanina - 20.3-10.el8_4.1 +- ci-Fix-requiring-device-number-on-EC2-derivatives-836.patch [bz#1942699] +- Resolves: bz#1942699 + ([Aliyun][RHEL8.4][cloud-init] cloud-init service failed to start with Alibaba instance [rhel-8.4.0.z]) + +* Tue Feb 02 2021 Miroslav Rezanina - 20.3-10.el8 +- ci-fix-a-typo-in-man-page-cloud-init.1-752.patch [bz#1913127] +- Resolves: bz#1913127 + (A typo in cloud-init man page) + +* Tue Jan 26 2021 Miroslav Rezanina - 20.3-9.el8 +- ci-DataSourceAzure-update-password-for-defuser-if-exist.patch [bz#1900892] +- ci-Revert-ssh_util-handle-non-default-AuthorizedKeysFil.patch [bz#1919972] +- Resolves: bz#1900892 + ([Azure] Update existing user password RHEL8x) +- Resolves: bz#1919972 + ([RHEL-8.4] ssh keys can be shared across users giving potential root access) + +* Thu Jan 21 2021 Miroslav Rezanina - 20.3-8.el8 +- ci-Missing-IPV6_AUTOCONF-no-to-render-sysconfig-dhcp6-s.patch [bz#1859695] +- Resolves: bz#1859695 + ([Cloud-init] DHCPv6 assigned address is not added to VM's interface) + +* Tue Jan 05 2021 Miroslav Rezanina - 20.3-7.el8 +- ci-Report-full-specific-version-with-cloud-init-version.patch [bz#1898949] +- Resolves: bz#1898949 + (cloud-init should report full specific full version with "cloud-init --version") + +* Mon Dec 14 2020 Miroslav Rezanina - 20.3-6.el8 +- ci-Installing-man-pages-in-the-correct-place-with-corre.patch [bz#1612573] +- ci-Adding-BOOTPROTO-dhcp-to-render-sysconfig-dhcp6-stat.patch [bz#1859695] +- ci-Fix-unit-failure-of-cloud-final.service-if-NetworkMa.patch [bz#1898943] +- ci-ssh_util-handle-non-default-AuthorizedKeysFile-confi.patch [bz#1862967] +- Resolves: bz#1612573 + (Man page scan results for cloud-init) +- Resolves: bz#1859695 + ([Cloud-init] DHCPv6 assigned address is not added to VM's interface) +- Resolves: bz#1898943 + ([rhel-8]cloud-final.service fails if NetworkManager not installed.) +- Resolves: bz#1862967 + ([cloud-init]Customize ssh AuthorizedKeysFile causes login failure) + +* Fri Nov 27 2020 Miroslav Rezanina - 20.3-5.el8 +- ci-network-Fix-type-and-respect-name-when-rendering-vla.patch [bz#1881462] +- Resolves: bz#1881462 + ([rhel8][cloud-init] ifup bond0.504 Error: Connection activation failed: No suitable device found for this connection) + +* Tue Nov 24 2020 Miroslav Rezanina - 20.3-4.el8 +- ci-Changing-permission-of-cloud-init-generator-to-755.patch [bz#1897528] +- Resolves: bz#1897528 + (Change permission on ./systemd/cloud-init-generator.tmpl to 755 instead of 771) + +* Fri Nov 13 2020 Miroslav Rezanina - 20.3-3.el8 +- ci--Removing-net-tools-dependency.patch [bz#1881871] +- ci--Adding-man-pages-to-Red-Hat-spec-file.patch [bz#1612573] +- Resolves: bz#1881871 + (Remove net-tools legacy dependency from spec file) +- Resolves: bz#1612573 + (Man page scan results for cloud-init) + +* Tue Nov 03 2020 Miroslav Rezanina - 20.3-2.el8 +- ci-Explicit-set-IPV6_AUTOCONF-and-IPV6_FORCE_ACCEPT_RA-.patch [bz#1889635] +- ci-Add-config-modules-for-controlling-IBM-PowerVM-RMC.-.patch [bz#1886430] +- Resolves: bz#1886430 + (Support for cloud-init config modules for PowerVM Hypervisor in Red Hat cloud-init) +- Resolves: bz#1889635 + (Add support for ipv6_autoconf on cloud-init-20.3) + +* Fri Oct 23 2020 Eduardo Otubo - 20.3-1.el8 +- Rebase to cloud-init 20.3 [bz#1885185] +- Resolves: bz#1885185 + ([RHEL-8.4.0] cloud-init rebase to 20.3) + +* Wed Sep 02 2020 Miroslav Rezanina - 19.4-11.el8 +- ci-cc_mounts-fix-incorrect-format-specifiers-316.patch [bz#1794664] +- Resolves: bz#1794664 + ([RHEL8] swapon fails with "swapfile has holes" when created on a xfs filesystem by cloud-init) + +* Mon Aug 31 2020 Miroslav Rezanina - 19.4-10.el8 +- ci-Changing-notation-of-subp-call.patch [bz#1839662] +- Resolves: bz#1839662 + ([ESXi][RHEL8.3][cloud-init]ERROR log in cloud-init.log after clone VM on ESXi platform) + +* Mon Aug 24 2020 Miroslav Rezanina - 19.4-9.el8 +- ci-Do-not-use-fallocate-in-swap-file-creation-on-xfs.-7.patch [bz#1794664] +- ci-swap-file-size-being-used-before-checked-if-str-315.patch [bz#1794664] +- ci-Detect-kernel-version-before-swap-file-creation-428.patch [bz#1794664] +- Resolves: bz#1794664 + ([RHEL8] swapon fails with "swapfile has holes" when created on a xfs filesystem by cloud-init) + +* Mon Aug 17 2020 Miroslav Rezanina - 19.4-8.el8 +- ci-When-tools.conf-does-not-exist-running-cmd-vmware-to.patch [bz#1839662] +- ci-ssh-exit-with-non-zero-status-on-disabled-user-472.patch [bz#1833874] +- Resolves: bz#1833874 + ([rhel-8.3]using root user error should cause a non-zero exit code) +- Resolves: bz#1839662 + ([ESXi][RHEL8.3][cloud-init]ERROR log in cloud-init.log after clone VM on ESXi platform) + +* Fri Jun 26 2020 Miroslav Rezanina - 19.4-7.el8 +- Fixing cloud-init-generator permissions [bz#1834173] +- Resolves: bz#1834173 + ([rhel-8.3]Incorrect ds-identify check in cloud-init-generator) + +* Thu Jun 25 2020 Miroslav Rezanina - 19.4-6.el8 +- ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch [bz#1822343] +- Resolves: bz#1822343 + ([RHEL8.3] Do not log IMDSv2 token values into cloud-init.log) + +* Wed Jun 24 2020 Miroslav Rezanina - 19.4-5.el8 +- ci-ec2-Do-not-log-IMDSv2-token-values-instead-use-REDAC.patch [bz#1822343] +- ci-Render-the-generator-from-template-instead-of-cp.patch [bz#1834173] +- ci-Change-from-redhat-to-rhel-in-systemd-generator-tmpl.patch [bz#1834173] +- ci-cloud-init.service.tmpl-use-rhel-instead-of-redhat-4.patch [bz#1834173] +- Resolves: bz#1822343 + ([RHEL8.3] Do not log IMDSv2 token values into cloud-init.log) +- Resolves: bz#1834173 + ([rhel-8.3]Incorrect ds-identify check in cloud-init-generator) + +* Tue Jun 09 2020 Miroslav Rezanina - 19.4-4.el8 +- ci-changing-ds-identify-patch-from-usr-lib-to-usr-libex.patch [bz#1834173] +- Resolves: bz#1834173 + ([rhel-8.3]Incorrect ds-identify check in cloud-init-generator) + +* Mon Jun 01 2020 Miroslav Rezanina - 19.4-3.el8 +- ci-Make-cloud-init.service-execute-after-network-is-up.patch [bz#1803928] +- Resolves: bz#1803928 + ([RHEL8.3] Race condition of starting cloud-init and NetworkManager) + +* Thu May 28 2020 Miroslav Rezanina - 19.4-2.el8 +- ci-cc_set_password-increase-random-pwlength-from-9-to-2.patch [bz#1812171] +- ci-utils-use-SystemRandom-when-generating-random-passwo.patch [bz#1812174] +- ci-Enable-ssh_deletekeys-by-default.patch [bz#1814152] +- ci-Remove-race-condition-between-cloud-init-and-Network.patch [bz#1840648] +- Resolves: bz#1812171 + (CVE-2020-8632 cloud-init: Too short random password length in cc_set_password in config/cc_set_passwords.py [rhel-8]) +- Resolves: bz#1812174 + (CVE-2020-8631 cloud-init: Use of random.choice when generating random password [rhel-8]) +- Resolves: bz#1814152 + (CVE-2018-10896 cloud-init: default configuration disabled deletion of SSH host keys [rhel-8]) +- Resolves: bz#1840648 + ([cloud-init][RHEL-8.2.0] /etc/resolv.conf lose config after reboot (initial instance is ok)) + +* Mon Apr 20 2020 Miroslav Rezanina - 19.4-1.el8.1 +- Rebase to cloud-init 19.4 [bz#1811912] +- Resolves: bz#1811912 + ([RHEL-8.2.1] cloud-init rebase to 19.4) + +* Tue Mar 10 2020 Miroslav Rezanina - 18.5-12.el8 +- ci-Remove-race-condition-between-cloud-init-and-Network.patch [bz#1807797] +- Resolves: bz#1807797 + ([cloud-init][RHEL-8.2.0] /etc/resolv.conf lose config after reboot (initial instance is ok)) + +* Thu Feb 20 2020 Miroslav Rezanina - 18.5-11.el8 +- ci-azure-avoid-re-running-cloud-init-when-instance-id-i.patch [bz#1788684] +- ci-net-skip-bond-interfaces-in-get_interfaces.patch [bz#1768770] +- ci-net-add-is_master-check-for-filtering-device-list.patch [bz#1768770] +- Resolves: bz#1768770 + (cloud-init complaining about enslaved mac) +- Resolves: bz#1788684 + ([RHEL-8] cloud-init Azure byte swap (hyperV Gen2 Only)) + +* Thu Feb 13 2020 Miroslav Rezanina - 18.5-10.el8 +- ci-cmd-main.py-Fix-missing-modules-init-key-in-modes-di.patch [bz#1802140] +- Resolves: bz#1802140 + ([cloud-init][RHEL8.2]cloud-init cloud-final.service fail with KeyError: 'modules-init' after upgrade to version 18.2-1.el7_6.1 in RHV) + +* Tue Jan 28 2020 Miroslav Rezanina - 18.5-9.el8 +- ci-Removing-cloud-user-from-wheel.patch [bz#1785648] +- Resolves: bz#1785648 + ([RHEL8]cloud-user added to wheel group and sudoers.d causes 'sudo -v' prompts for passphrase) + +* Fri Nov 22 2019 Miroslav Rezanina - 18.5-8.el8 +- ci-Fix-for-network-configuration-not-persisting-after-r.patch [bz#1706482] +- ci-util-json.dumps-on-python-2.7-will-handle-UnicodeDec.patch [bz#1744718] +- Resolves: bz#1706482 + ([cloud-init][RHVM]cloud-init network configuration does not persist reboot [RHEL 8.2.0]) +- Resolves: bz#1744718 + ([cloud-init][RHEL8][OpenStack] cloud-init can't persist instance-data.json) + +* Mon Jul 15 2019 Miroslav Rezanina - 18.5-7.el8 +- Fixing TPS [bz#1729864] +- Resolves: bz#1729864 + (cloud-init tps fail) + +* Thu Jul 04 2019 Miroslav Rezanina - 18.5-6.el8 +- ci-Revert-azure-ensure-that-networkmanager-hook-script-.patch [bz#1692914] +- ci-Azure-Return-static-fallback-address-as-if-failed-to.patch [bz#1691986] +- Resolves: bz#1691986 + ([Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure) +- Resolves: bz#1692914 + ([8.1] [WALA][cloud] cloud-init dhclient-hook script has some unexpected side-effects on Azure) + +* Mon Jun 03 2019 Miroslav Rezanina - 18.5-4.el8 +- ci-Azure-Ensure-platform-random_seed-is-always-serializ.patch [bz#1691986] +- ci-DatasourceAzure-add-additional-logging-for-azure-dat.patch [bz#1691986] +- ci-Azure-Changes-to-the-Hyper-V-KVP-Reporter.patch [bz#1691986] +- ci-DataSourceAzure-Adjust-timeout-for-polling-IMDS.patch [bz#1691986] +- ci-cc_mounts-check-if-mount-a-on-no-change-fstab-path.patch [bz#1691986] +- Resolves: bz#1691986 + ([Azure] [RHEL 8.1] Cloud-init fixes to support fast provisioning for Azure) + +* Tue Apr 16 2019 Danilo Cesar Lemes de Paula - 18.5-3.el8 +- ci-Adding-gating-tests-for-Azure-ESXi-and-AWS.patch [bz#1682786] +- Resolves: bz#1682786 + (cloud-init changes blocked until gating tests are added) + +* Wed Apr 10 2019 Danilo C. L. de Paula - 18.5-2 +- Adding gating.yaml file +- Resolves: rhbz#1682786 + (cloud-init changes blocked until gating tests are added) + + +* Wed Apr 10 2019 Danilo de Paula - 18.2-6.el8 +- ci-net-Make-sysconfig-renderer-compatible-with-Network-.patch [bz#1602784] +- Resolves: bz#1602784 + (cloud-init: Sometimes image boots fingerprints is configured, there's a network device present but it's not configured) + +* Fri Jan 18 2019 Miroslav Rezanina - 18.2-5.el8 +- ci-Fix-string-missmatch-when-mounting-ntfs.patch [bz#1664227] +- Resolves: bz#1664227 + ([Azure]String missmatch causes the /dev/sdb1 mounting failed after stop&start VM) + +* Thu Jan 10 2019 Miroslav Rezanina - 18.2-4.el8 +- ci-Enable-cloud-init-by-default-on-vmware.patch [bz#1644335] +- Resolves: bz#1644335 + ([ESXi][RHEL8.0]Enable cloud-init by default on VMware) + +* Wed Nov 28 2018 Miroslav Rezanina - 18.2-3.el8 +- ci-Adding-systemd-mount-options-to-wait-for-cloud-init.patch [bz#1615599] +- ci-Azure-Ignore-NTFS-mount-errors-when-checking-ephemer.patch [bz#1615599] +- ci-azure-Add-reported-ready-marker-file.patch [bz#1615599] +- ci-Adding-disk_setup-to-rhel-cloud.cfg.patch [bz#1615599] +- Resolves: bz#1615599 + ([Azure] cloud-init fails to mount /dev/sdb1 after stop(deallocate)&&start VM) + +* Tue Nov 06 2018 Miroslav Rezanina - 18.2-2.el7 +- Revert "remove 'tee' command from logging configuration" [bz#1626117] +- Resolves: rhbz#1626117] + (cloud-init-0.7.9-9 doesn't feed cloud-init-output.log) + +* Fri Jun 29 2018 Miroslav Rezanina - 18.2-1.el7 +- Rebase to 18.2 [bz#1515909] + Resolves: rhbz#1515909 + +* Tue Feb 13 2018 Ryan McCabe 0.7.9-24 +- Set DHCP_HOSTNAME on Azure to allow for the hostname to be + published correctly when bouncing the network. + Resolves: rhbz#1434109 + +* Mon Jan 15 2018 Ryan McCabe 0.7.9-23 +- Fix a bug tha caused cloud-init to fail as a result of trying + to rename bonds. + Resolves: rhbz#1512247 + +* Mon Jan 15 2018 Ryan McCabe 0.7.9-22 +- Apply patch from -21 + Resolves: rhbz#1489270 + +* Mon Jan 15 2018 Ryan McCabe 0.7.9-21 +- sysconfig: Fix a potential traceback introduced in the + 0.7.9-17 build + Resolves: rhbz#1489270 + +* Sun Dec 17 2017 Ryan McCabe 0.7.9-20 +- sysconfig: Correct rendering for dhcp on ipv6 + Resolves: rhbz#1519271 + +* Thu Nov 30 2017 Ryan McCabe 0.7.9-19 +- sysconfig: Fix rendering of default gateway for ipv6 + Resolves: rhbz#1492726 + +* Fri Nov 24 2017 Ryan McCabe 0.7.9-18 +- Start the cloud-init init local service after the dbus socket is created + so that the hostnamectl command works. + Resolves: rhbz#1450521 + +* Tue Nov 21 2017 Ryan McCabe 0.7.9-17 +- Correctly render DNS and DOMAIN for sysconfig + Resolves: rhbz#1489270 + +* Mon Nov 20 2017 Ryan McCabe 0.7.9-16 +- Disable NetworkManager management of resolv.conf if nameservers + are specified by configuration. + Resolves: rhbz#1454491 + +* Mon Nov 13 2017 Ryan McCabe 0.7.9-15 +- Fix a null reference error in the rh_subscription module + Resolves: rhbz#1498974 + +* Mon Nov 13 2017 Ryan McCabe 0-7.9-14 +- Include gateway if it's included in subnet configration + Resolves: rhbz#1492726 + +* Sun Nov 12 2017 Ryan McCabe 0-7.9-13 +- Do proper cleanup of systemd units when upgrading from versions + 0.7.9-3 through 0.7.9-8. + Resolves: rhbz#1465730 + +* Thu Nov 09 2017 Ryan McCabe 0.7.9-12 +- Prevent Azure NM and dhclient hooks from running when cloud-init is + disabled (rhbz#1474226) + +* Tue Oct 31 2017 Ryan McCabe 0.7.9-11 +- Fix rendering of multiple static IPs per interface file + Resolves: rhbz#bz1497954 + +* Tue Sep 26 2017 Ryan McCabe 0.7.9-10 +- AliCloud: Add support for the Alibaba Cloud datasource (rhbz#1482547) + +* Thu Jun 22 2017 Lars Kellogg-Stedman 0.7.9-9 +- RHEL/CentOS: Fix default routes for IPv4/IPv6 configuration. (rhbz#1438082) +- azure: ensure that networkmanager hook script runs (rhbz#1440831 rhbz#1460206) +- Fix ipv6 subnet detection (rhbz#1438082) + +* Tue May 23 2017 Lars Kellogg-Stedman 0.7.9-8 +- Update patches + +* Mon May 22 2017 Lars Kellogg-Stedman 0.7.9-7 +- Add missing sysconfig unit test data (rhbz#1438082) +- Fix dual stack IPv4/IPv6 configuration for RHEL (rhbz#1438082) +- sysconfig: Raise ValueError when multiple default gateways are present. (rhbz#1438082) +- Bounce network interface for Azure when using the built-in path. (rhbz#1434109) +- Do not write NM_CONTROLLED=no in generated interface config files (rhbz#1385172) + +* Wed May 10 2017 Lars Kellogg-Stedman 0.7.9-6 +- add power-state-change module to cloud_final_modules (rhbz#1252477) +- remove 'tee' command from logging configuration (rhbz#1424612) +- limit permissions on def_log_file (rhbz#1424612) +- Bounce network interface for Azure when using the built-in path. (rhbz#1434109) +- OpenStack: add 'dvs' to the list of physical link types. (rhbz#1442783) + +* Wed May 10 2017 Lars Kellogg-Stedman 0.7.9-5 +- systemd: replace generator with unit conditionals (rhbz#1440831) + +* Thu Apr 13 2017 Charalampos Stratakis 0.7.9-4 +- Import to RHEL 7 +Resolves: rhbz#1427280 + +* Tue Mar 07 2017 Lars Kellogg-Stedman 0.7.9-3 +- fixes for network config generation +- avoid dependency cycle at boot (rhbz#1420946) + +* Tue Jan 17 2017 Lars Kellogg-Stedman 0.7.9-2 +- use timeout from datasource config in openstack get_data (rhbz#1408589) + +* Thu Dec 01 2016 Lars Kellogg-Stedman - 0.7.9-1 +- Rebased on upstream 0.7.9. +- Remove dependency on run-parts + +* Wed Jan 06 2016 Lars Kellogg-Stedman - 0.7.6-8 +- make rh_subscription plugin do nothing in the absence of a valid + configuration [RH:1295953] +- move rh_subscription module to cloud_config stage + +* Wed Jan 06 2016 Lars Kellogg-Stedman - 0.7.6-7 +- correct permissions on /etc/ssh/sshd_config [RH:1296191] + +* Thu Sep 03 2015 Lars Kellogg-Stedman - 0.7.6-6 +- rebuild for ppc64le + +* Tue Jul 07 2015 Lars Kellogg-Stedman - 0.7.6-5 +- bump revision for new build + +* Tue Jul 07 2015 Lars Kellogg-Stedman - 0.7.6-4 +- ensure rh_subscription plugin is enabled by default + +* Wed Apr 29 2015 Lars Kellogg-Stedman - 0.7.6-3 +- added dependency on python-jinja2 [RH:1215913] +- added rhn_subscription plugin [RH:1227393] +- require pyserial to support smartos data source [RH:1226187] + +* Fri Jan 16 2015 Lars Kellogg-Stedman - 0.7.6-2 +- Rebased RHEL version to Fedora rawhide +- Backported fix for https://bugs.launchpad.net/cloud-init/+bug/1246485 +- Backported fix for https://bugs.launchpad.net/cloud-init/+bug/1411829 + +* Fri Nov 14 2014 Colin Walters - 0.7.6-1 +- New upstream version [RH:974327] +- Drop python-cheetah dependency (same as above bug)