diff -Nru cloud-init-22.2-3338-g9dce9fa0/cloudinit/config/cc_ubuntu_autoinstall.py cloud-init-22.2-3339-g8bab5a13/cloudinit/config/cc_ubuntu_autoinstall.py --- cloud-init-22.2-3338-g9dce9fa0/cloudinit/config/cc_ubuntu_autoinstall.py 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/cloudinit/config/cc_ubuntu_autoinstall.py 2022-07-16 22:31:46.000000000 +0000 @@ -0,0 +1,141 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +"""Autoinstall: Support ubuntu live-server autoinstall syntax.""" + +import re +from textwrap import dedent + +from cloudinit import log as logging +from cloudinit.config.schema import ( + MetaSchema, + SchemaProblem, + SchemaValidationError, + get_meta_doc, +) +from cloudinit.settings import PER_ONCE +from cloudinit.subp import subp + +LOG = logging.getLogger(__name__) + +distros = ["ubuntu"] + +meta: MetaSchema = { + "id": "cc_ubuntu_autoinstall", + "name": "Autoinstall", + "title": "Support Ubuntu live-server install syntax", + "description": dedent( + """\ + Ubuntu's autoinstall syntax supports single-system automated installs + in either the live-server or live-desktop installers. + When "autoinstall" directives are provided in either + #cloud-config user-data or ``/etc/cloud/cloud.cfg.d`` validate + minimal autoinstall schema adherance and emit a warning if the + live-installer is not present. + + The live-installer will use autoinstall directives to seed answers to + configuration prompts during system install to allow for a + "touchless" Ubuntu system install. + + For more details on Ubuntu's autoinstaller: + https://ubuntu.com/server/docs/install/autoinstall + """ + ), + "distros": distros, + "examples": [ + dedent( + """\ + # Tell the live-server installer to provide dhcp6 network config + # and LVM on a disk matching the serial number prefix CT + autoinstall: + version: 1 + network: + version: 2 + ethernets: + enp0s31f6: + dhcp6: yes + storage: + layout: + name: lvm + match: + serial: CT* + """ + ) + ], + "frequency": PER_ONCE, +} + +__doc__ = get_meta_doc(meta) + + +LIVE_INSTALLER_SNAPS = ("subiquity", "ubuntu-desktop-installer") + + +def handle(name, cfg, cloud, log, _args): + + if "autoinstall" not in cfg: + LOG.debug( + "Skipping module named %s, no 'autoinstall' key in configuration", + name, + ) + return + + snap_list, _ = subp(["snap", "list"]) + installer_present = None + for snap_name in LIVE_INSTALLER_SNAPS: + if re.search(snap_name, snap_list): + installer_present = snap_name + if not installer_present: + LOG.warning( + "Skipping autoinstall module. Expected one of the Ubuntu" + " installer snap packages to be present: %s", + ", ".join(LIVE_INSTALLER_SNAPS), + ) + return + validate_config_schema(cfg) + LOG.debug( + "Valid autoinstall schema. Config will be processed by %s", + installer_present, + ) + + +def validate_config_schema(cfg): + """Supplemental runtime schema validation for autoinstall yaml. + + Schema validation issues currently result in a warning log currently which + can be easily ignored because warnings do not bubble up to cloud-init + status output. + + In the case of the live-installer, we want cloud-init to raise an error + to set overall cloud-init status to 'error' so it is more discoverable + in installer environments. + + # TODO(Drop this validation When cloud-init schema is strict and errors) + + :raise: SchemaValidationError if any known schema values are present. + """ + autoinstall_cfg = cfg["autoinstall"] + if not isinstance(autoinstall_cfg, dict): + raise SchemaValidationError( + [ + SchemaProblem( + "autoinstall", + "Expected dict type but found:" + f" {type(autoinstall_cfg).__name__}", + ) + ] + ) + + if "version" not in autoinstall_cfg: + raise SchemaValidationError( + [SchemaProblem("autoinstall", "Missing required 'version' key")] + ) + elif not isinstance(autoinstall_cfg.get("version"), int): + raise SchemaValidationError( + [ + SchemaProblem( + "autoinstall.version", + f"Expected int type but found:" + f" {type(autoinstall_cfg['version']).__name__}", + ) + ] + ) diff -Nru cloud-init-22.2-3338-g9dce9fa0/cloudinit/config/schemas/schema-cloud-config-v1.json cloud-init-22.2-3339-g8bab5a13/cloudinit/config/schemas/schema-cloud-config-v1.json --- cloud-init-22.2-3338-g9dce9fa0/cloudinit/config/schemas/schema-cloud-config-v1.json 2022-07-15 22:36:37.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/cloudinit/config/schemas/schema-cloud-config-v1.json 2022-07-16 22:31:46.000000000 +0000 @@ -214,6 +214,22 @@ }, "minProperties": 1 }, + "cc_ubuntu_autoinstall": { + "type": "object", + "properties": { + "autoinstall": { + "description": "Opaque autoinstall schema definition for Ubuntu autoinstall. Full schema processed by live-installer. See: https://ubuntu.com/server/docs/install/autoinstall-reference", + "type": "object", + "properties": { + "version": { + "type": "integer" + } + }, + "required": ["version"] + } + }, + "additionalProperties": true + }, "cc_apk_configure": { "type": "object", "properties": { @@ -2449,6 +2465,7 @@ { "$ref": "#/$defs/cc_apk_configure" }, { "$ref": "#/$defs/cc_apt_configure" }, { "$ref": "#/$defs/cc_apt_pipelining" }, + { "$ref": "#/$defs/cc_ubuntu_autoinstall"}, { "$ref": "#/$defs/cc_bootcmd" }, { "$ref": "#/$defs/cc_byobu" }, { "$ref": "#/$defs/cc_ca_certs" }, diff -Nru cloud-init-22.2-3338-g9dce9fa0/config/cloud.cfg.tmpl cloud-init-22.2-3339-g8bab5a13/config/cloud.cfg.tmpl --- cloud-init-22.2-3338-g9dce9fa0/config/cloud.cfg.tmpl 2022-07-15 22:36:37.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/config/cloud.cfg.tmpl 2022-07-16 22:31:46.000000000 +0000 @@ -114,6 +114,9 @@ {% if variant in ["ubuntu", "unknown", "debian"] %} - snap {% endif %} +{% if variant in ["ubuntu"] %} + - ubuntu_autoinstall +{% endif %} {% if variant not in ["photon"] %} - ssh-import-id {% if variant not in ["rhel"] %} diff -Nru cloud-init-22.2-3338-g9dce9fa0/debian/changelog cloud-init-22.2-3339-g8bab5a13/debian/changelog --- cloud-init-22.2-3338-g9dce9fa0/debian/changelog 2022-07-15 22:36:40.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/debian/changelog 2022-07-16 22:31:50.000000000 +0000 @@ -1,8 +1,8 @@ -cloud-init (22.2-3338-g9dce9fa0-0ubuntu1+1952~trunk~ubuntu21.10.1) impish; urgency=low +cloud-init (22.2-3339-g8bab5a13-0ubuntu1+1952~trunk~ubuntu21.10.1) impish; urgency=low * Auto build. - -- cloud-init Commiters Fri, 15 Jul 2022 22:36:40 +0000 + -- cloud-init Commiters Sat, 16 Jul 2022 22:31:50 +0000 cloud-init (22.2-0ubuntu1~21.10.2) UNRELEASED; urgency=medium diff -Nru cloud-init-22.2-3338-g9dce9fa0/debian/git-build-recipe.manifest cloud-init-22.2-3339-g8bab5a13/debian/git-build-recipe.manifest --- cloud-init-22.2-3338-g9dce9fa0/debian/git-build-recipe.manifest 2022-07-15 22:36:40.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/debian/git-build-recipe.manifest 2022-07-16 22:31:50.000000000 +0000 @@ -1,3 +1,3 @@ -# git-build-recipe format 0.4 deb-version 22.2-3338-g9dce9fa0-0ubuntu1+1952~trunk -lp:cloud-init git-commit:9dce9fa0df648727e04c74ba95665f75749cdfaf +# git-build-recipe format 0.4 deb-version 22.2-3339-g8bab5a13-0ubuntu1+1952~trunk +lp:cloud-init git-commit:8bab5a138ec765f34d9565dc255e9c8e87fb5607 merge ubuntu-pkg lp:cloud-init git-commit:874038bd27ad6b7d8d7726c98be2a270a07d6500 diff -Nru cloud-init-22.2-3338-g9dce9fa0/.pc/.quilt_patches cloud-init-22.2-3339-g8bab5a13/.pc/.quilt_patches --- cloud-init-22.2-3338-g9dce9fa0/.pc/.quilt_patches 2022-07-15 22:36:40.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/.pc/.quilt_patches 2022-07-16 22:31:50.000000000 +0000 @@ -1 +1 @@ -/home/buildd/build-RECIPEBRANCHBUILD-3395225/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches +/home/buildd/build-RECIPEBRANCHBUILD-3395627/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches diff -Nru cloud-init-22.2-3338-g9dce9fa0/.pc/.quilt_series cloud-init-22.2-3339-g8bab5a13/.pc/.quilt_series --- cloud-init-22.2-3338-g9dce9fa0/.pc/.quilt_series 2022-07-15 22:36:40.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/.pc/.quilt_series 2022-07-16 22:31:50.000000000 +0000 @@ -1 +1 @@ -/home/buildd/build-RECIPEBRANCHBUILD-3395225/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series +/home/buildd/build-RECIPEBRANCHBUILD-3395627/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series diff -Nru cloud-init-22.2-3338-g9dce9fa0/tests/integration_tests/modules/test_ubuntu_autoinstall.py cloud-init-22.2-3339-g8bab5a13/tests/integration_tests/modules/test_ubuntu_autoinstall.py --- cloud-init-22.2-3338-g9dce9fa0/tests/integration_tests/modules/test_ubuntu_autoinstall.py 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/tests/integration_tests/modules/test_ubuntu_autoinstall.py 2022-07-16 22:31:46.000000000 +0000 @@ -0,0 +1,26 @@ +"""Integration tests for cc_ubuntu_autoinstall happy path""" + +import pytest + +USER_DATA = """\ +#cloud-config +autoinstall: + version: 1 + cloudinitdoesnotvalidateotherkeyschema: true +snap: + commands: + - snap install subiquity --classic +""" + + +LOG_MSG = "Valid autoinstall schema. Config will be processed by subiquity" + + +@pytest.mark.ubuntu +@pytest.mark.user_data(USER_DATA) +class TestUbuntuAutoinstall: + def test_autoinstall_schema_valid_when_snap_present(self, class_client): + """autoinstall directives will pass when snap is present""" + assert "subiquity" in class_client.execute(["snap", "list"]).stdout + log = class_client.read_from_file("/var/log/cloud-init.log") + assert LOG_MSG in log diff -Nru cloud-init-22.2-3338-g9dce9fa0/tests/unittests/config/test_cc_ubuntu_autoinstall.py cloud-init-22.2-3339-g8bab5a13/tests/unittests/config/test_cc_ubuntu_autoinstall.py --- cloud-init-22.2-3338-g9dce9fa0/tests/unittests/config/test_cc_ubuntu_autoinstall.py 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/tests/unittests/config/test_cc_ubuntu_autoinstall.py 2022-07-16 22:31:46.000000000 +0000 @@ -0,0 +1,141 @@ +# This file is part of cloud-init. See LICENSE file for license information. + +import logging +from unittest import mock + +import pytest + +from cloudinit.config import cc_ubuntu_autoinstall +from cloudinit.config.schema import ( + SchemaValidationError, + get_schema, + validate_cloudconfig_schema, +) +from tests.unittests.helpers import skipUnlessJsonSchema +from tests.unittests.util import get_cloud + +LOG = logging.getLogger(__name__) + +MODPATH = "cloudinit.config.cc_ubuntu_autoinstall." + +SAMPLE_SNAP_LIST_OUTPUT = """ +Name Version Rev Tracking ... +core20 20220527 1518 latest/stable ... +lxd git-69dc707 23315 latest/edge ... +""" +SAMPLE_SNAP_LIST_SUBIQUITY = ( + SAMPLE_SNAP_LIST_OUTPUT + + """ +subiquity 22.06.01 23315 latest/stable ... +""" +) +SAMPLE_SNAP_LIST_DESKTOP_INSTALLER = ( + SAMPLE_SNAP_LIST_OUTPUT + + """ +ubuntu-desktop-installer 22.06.01 23315 latest/stable ... +""" +) + + +class TestvalidateConfigSchema: + @pytest.mark.parametrize( + "src_cfg,error_msg", + [ + pytest.param( + {"autoinstall": 1}, + "autoinstall: Expected dict type but found: int", + id="err_non_dict", + ), + pytest.param( + {"autoinstall": {}}, + "autoinstall: Missing required 'version' key", + id="err_require_version_key", + ), + pytest.param( + {"autoinstall": {"version": "v1"}}, + "autoinstall.version: Expected int type but found: str", + id="err_version_non_int", + ), + ], + ) + def test_runtime_validation_errors(self, src_cfg, error_msg): + """cloud-init raises errors at runtime on invalid autoinstall config""" + with pytest.raises(SchemaValidationError, match=error_msg): + cc_ubuntu_autoinstall.validate_config_schema(src_cfg) + + +@mock.patch(MODPATH + "subp") +class TestHandleAutoinstall: + """Test cc_ubuntu_autoinstall handling of config.""" + + @pytest.mark.parametrize( + "cfg,snap_list,subp_calls,logs", + [ + pytest.param( + {}, + SAMPLE_SNAP_LIST_OUTPUT, + [], + ["Skipping module named name, no 'autoinstall' key"], + id="skip_no_cfg", + ), + pytest.param( + {"autoinstall": {"version": 1}}, + SAMPLE_SNAP_LIST_OUTPUT, + [mock.call(["snap", "list"])], + [ + "Skipping autoinstall module. Expected one of the Ubuntu" + " installer snap packages to be present: subiquity," + " ubuntu-desktop-installer" + ], + id="valid_autoinstall_schema_checks_snaps", + ), + pytest.param( + {"autoinstall": {"version": 1}}, + SAMPLE_SNAP_LIST_SUBIQUITY, + [mock.call(["snap", "list"])], + [ + "Valid autoinstall schema. Config will be processed by" + " subiquity" + ], + id="valid_autoinstall_schema_sees_subiquity", + ), + pytest.param( + {"autoinstall": {"version": 1}}, + SAMPLE_SNAP_LIST_DESKTOP_INSTALLER, + [mock.call(["snap", "list"])], + [ + "Valid autoinstall schema. Config will be processed by" + " ubuntu-desktop-installer" + ], + id="valid_autoinstall_schema_sees_desktop_installer", + ), + ], + ) + def test_handle_autoinstall_cfg( + self, subp, cfg, snap_list, subp_calls, logs, caplog + ): + subp.return_value = snap_list, "" + cloud = get_cloud(distro="ubuntu") + cc_ubuntu_autoinstall.handle("name", cfg, cloud, LOG, None) + assert subp_calls == subp.call_args_list + for log in logs: + assert log in caplog.text + + +class TestAutoInstallSchema: + @pytest.mark.parametrize( + "config, error_msg", + ( + ( + {"autoinstall": {}}, + "autoinstall: 'version' is a required property", + ), + ), + ) + @skipUnlessJsonSchema() + def test_schema_validation(self, config, error_msg): + if error_msg is None: + validate_cloudconfig_schema(config, get_schema(), strict=True) + else: + with pytest.raises(SchemaValidationError, match=error_msg): + validate_cloudconfig_schema(config, get_schema(), strict=True) diff -Nru cloud-init-22.2-3338-g9dce9fa0/tests/unittests/config/test_schema.py cloud-init-22.2-3339-g8bab5a13/tests/unittests/config/test_schema.py --- cloud-init-22.2-3338-g9dce9fa0/tests/unittests/config/test_schema.py 2022-07-15 22:36:37.000000000 +0000 +++ cloud-init-22.2-3339-g8bab5a13/tests/unittests/config/test_schema.py 2022-07-16 22:31:46.000000000 +0000 @@ -164,6 +164,7 @@ {"$ref": "#/$defs/cc_apk_configure"}, {"$ref": "#/$defs/cc_apt_configure"}, {"$ref": "#/$defs/cc_apt_pipelining"}, + {"$ref": "#/$defs/cc_ubuntu_autoinstall"}, {"$ref": "#/$defs/cc_bootcmd"}, {"$ref": "#/$defs/cc_byobu"}, {"$ref": "#/$defs/cc_ca_certs"},