Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create a bootc module #5932

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions cloudinit/config/cc_bootc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Bootc: Switch to a different bootc image."""

import logging
import os

from cloudinit import subp, util
from cloudinit.cloud import Cloud
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema
from cloudinit.distros import ALL_DISTROS
from cloudinit.settings import PER_INSTANCE

meta: MetaSchema = {
"id": "cc_bootc",
"distros": [ALL_DISTROS],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": ["bootc"],
}

LOG = logging.getLogger(__name__)

def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
bootc = cfg.get('bootc')
if not bootc:
LOG.warning("No 'bootc' configuration found, skipping.")
return

image = util.get_cfg_option_str(bootc, "image")
if not image:
raise ValueError("No 'image' specified in the 'bootc' configuration.")

# If Podman is not installed, install it
if not os.path.exists("/usr/bin/podman"):
install_podman(cloud, image)

LOG.info(f"Switching to bootc image: {image}")

try:
pull_image(image)
install_to_existing_root(image)
except Exception as e:
LOG.error(f"Failed to switch to bootc image: {e}")
raise

LOG.info("Successfully switched to bootc image")
_fire_reboot()


def install_podman(cloud: Cloud, image: str) -> None:
LOG.info(f"Installing Podman")

try:
cloud.distri.install_packages(["podman"])
except Exception as e:
LOG.error(f"Failed to install Podman: {e}")
raise


def pull_image(image: str) -> None:
LOG.info(f"Pulling bootc image: {image}")

try:
command = ["podman", "pull", "--tls-verify=false", image]
subp.subp(command)
LOG.info(f"Successfully executed: {' '.join(command)}")
except Exception as e:
LOG.error(f"Command failed with error: {e}")
raise


def install_to_existing_root(image: str) -> None:
LOG.info(f"Installing bootc to existing root")

try:
command = [
"podman",
"run",
"--rm",
"--privileged",
"--volume", "/dev:/dev",
"--volume", "/var/lib/containers:/var/lib/containers",
"--volume", "/:/target",
"--pid=host",
"--tls-verify=false",
"--security-opt", "label=type:unconfined_t",
image,
"bootc",
"install",
"to-existing-root",
]
subp.subp(command)
LOG.info(f"Successfully executed: {' '.join(command)}")
except Exception as e:
LOG.error(f"Command failed with error: {e}")
raise


# Taken from cc_package_update_upgrade_install.py
def _fire_reboot(
wait_attempts: int = 6, initial_sleep: int = 1, backoff: int = 2
):
"""Run a reboot command and panic if it doesn't happen fast enough."""
subp.subp(REBOOT_CMD)
start = time.monotonic()
wait_time = initial_sleep
for _i in range(wait_attempts):
time.sleep(wait_time)
wait_time *= backoff
elapsed = time.monotonic() - start
LOG.debug("Rebooted, but still running after %s seconds", int(elapsed))
# If we got here, not good
elapsed = time.monotonic() - start
raise RuntimeError(
"Reboot did not happen after %s seconds!" % (int(elapsed))
)
16 changes: 15 additions & 1 deletion cloudinit/config/schemas/schema-cloud-config-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"apt_configure",
"apt-pipelining",
"apt_pipelining",
"bootc",
"bootcmd",
"byobu",
"ca-certs",
Expand Down Expand Up @@ -1143,6 +1144,15 @@
}
}
},
"cc_bootc": {
"type": "object",
"properties": {
"image": {
"type": "string",
"description": "Target image to use for the next boot"
}
}
},
"cc_bootcmd": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -3787,6 +3797,9 @@
{
"$ref": "#/$defs/cc_ubuntu_autoinstall"
},
{
"$ref": "#/$defs/cc_bootc"
},
{
"$ref": "#/$defs/cc_bootcmd"
},
Expand Down Expand Up @@ -3949,6 +3962,7 @@
"apt_upgrade": {},
"authkey_hash": {},
"autoinstall": {},
"bootc": {},
"bootcmd": {},
"byobu_by_default": {},
"ca-certs": {},
Expand Down Expand Up @@ -4039,4 +4053,4 @@
"zypper": {}
},
"additionalProperties": false
}
}
3 changes: 3 additions & 0 deletions config/cloud.cfg.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ cloud_config_modules:
{% if variant in ["ubuntu"] %}
- wireguard
{% endif %}
{% if variant in ["fedora"] or is_rhel %}
- bootc
{% endif %}
{% if variant in ["debian", "ubuntu", "unknown"] %}
- snap
{% endif %}
Expand Down
9 changes: 9 additions & 0 deletions doc/examples/cloud-config-bootc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#cloud-config

# bootc
# default: none
# This allows you to target a specific bootc image and switch to it
# If a reboot is required, the system will reboot into the new image
# and cloud-init will run again
bootc:
image: quay.io/myorg/myimage:latest
Loading