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(raspberry-pi-os): Add Raspberry Pi OS support #5827

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
build
cloud_init.egg-info
dist
*.deb
*.tar.xz
*.tar.gz
*.build
*.dsc
*.changes
*.buildinfo
*.pyc
__pycache__
.tox
Expand Down Expand Up @@ -37,3 +44,4 @@ cloud-init_*.upload

# user test settings
tests/integration_tests/user_settings.py

2 changes: 1 addition & 1 deletion cloudinit/config/cc_apt_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

meta: MetaSchema = {
"id": "cc_apt_configure",
"distros": ["ubuntu", "debian"],
"distros": ["ubuntu", "debian", "raspberry-pi-os"],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": [],
}
Expand Down
2 changes: 1 addition & 1 deletion cloudinit/config/cc_apt_pipelining.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

meta: MetaSchema = {
"id": "cc_apt_pipelining",
"distros": ["ubuntu", "debian"],
"distros": ["ubuntu", "debian", "raspberry-pi-os"],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": ["apt_pipelining"],
}
Expand Down
2 changes: 1 addition & 1 deletion cloudinit/config/cc_byobu.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

meta: MetaSchema = {
"id": "cc_byobu",
"distros": ["ubuntu", "debian"],
"distros": ["ubuntu", "debian", "raspberry-pi-os"],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": [],
}
Expand Down
11 changes: 9 additions & 2 deletions cloudinit/config/cc_ca_certs.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"alpine",
"debian",
"fedora",
"raspberry-pi-os",
"rhel",
"opensuse",
"opensuse-microos",
Expand Down Expand Up @@ -157,10 +158,16 @@ def disable_default_ca_certs(distro_name, distro_cfg):
"""
if distro_name in ["rhel", "photon"]:
remove_default_ca_certs(distro_cfg)
elif distro_name in ["alpine", "aosc", "debian", "ubuntu"]:
elif distro_name in [
"alpine",
"aosc",
"debian",
"raspberry-pi-os",
"ubuntu",
]:
disable_system_ca_certs(distro_cfg)

if distro_name in ["debian", "ubuntu"]:
if distro_name in ["debian", "raspberry-pi-os", "ubuntu"]:
debconf_sel = (
"ca-certificates ca-certificates/trust_new_crts " + "select no"
)
Expand Down
129 changes: 129 additions & 0 deletions cloudinit/config/cc_netplan_nm_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Copyright (C) 2024, Raspberry Pi Ltd.
#
# Author: Paul Oberosler <paul.oberosler@raspberrypi.com>
#
# This file is part of cloud-init. See LICENSE file for license information.

from cloudinit import subp
from cloudinit.cloud import Cloud
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema
from cloudinit.distros import ALL_DISTROS
from cloudinit.net.netplan import CLOUDINIT_NETPLAN_FILE
from cloudinit.settings import PER_INSTANCE
import logging
import os
import re

LOG = logging.getLogger(__name__)

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


def exec_cmd(command: str) -> str | None:
try:
result = subp.subp(command)
if result.stdout is not None:
return result.stdout
except subp.ProcessExecutionError as e:
LOG.error("Failed to execute command: %s", e)
return None
LOG.debug("Command has no stdout: %s", command)
return None


def get_netplan_generated_configs() -> list[str]:
"""Get the UUIDs of all connections starting with 'netplan-'."""
output = exec_cmd(["nmcli", "connection", "show"])
if output is None:
return []

netplan_conns = []
for line in output.splitlines():
if line.startswith("netplan-"):
parts = line.split()
if len(parts) > 1:
# name = parts[0]
uuid = parts[1]
netplan_conns.append(uuid)
return netplan_conns


def get_connection_object_path(uuid: str) -> str | None:
"""Get the D-Bus object path for a connection by UUID."""
output = exec_cmd(
[
"busctl",
"call",
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager/Settings",
"org.freedesktop.NetworkManager.Settings",
"GetConnectionByUuid",
"s",
uuid,
]
)

path_match = (
re.search(
r'o\s+"(/org/freedesktop/NetworkManager/Settings/\d+)"', output
)
if output
else None
)
if path_match:
return path_match.group(1)
else:
LOG.error("Failed to find object path for connection: %s", uuid)
return None


def save_connection(obj_path: str) -> None:
"""Call the Save method on the D-Bus obj path for a connection."""
result = exec_cmd(
[
"busctl",
"call",
"org.freedesktop.NetworkManager",
obj_path,
"org.freedesktop.NetworkManager.Settings.Connection",
"Save",
]
)

if result is None:
LOG.error("Failed to save connection: %s", obj_path)
else:
LOG.debug("Saved connection: %s", obj_path)


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
LOG.debug("Applying netplan patch")

# remove cloud-init file after NetworkManager has generated
# replacement netplan configurations to avoid conflicts in the
# future

try:
np_conns = get_netplan_generated_configs()
if not np_conns:
LOG.debug("No netplan connections found")
return

for conn_uuid in np_conns:
obj_path = get_connection_object_path(conn_uuid)
if obj_path is None:
continue
save_connection(obj_path)

os.remove(CLOUDINIT_NETPLAN_FILE)
LOG.debug("Netplan cfg has been patched: %s", CLOUDINIT_NETPLAN_FILE)
except subp.ProcessExecutionError as e:
LOG.error("Failed to patch netplan cfg: %s", e)
except Exception as e:
LOG.error("Failed to patch netplan cfg: %s", e)
6 changes: 6 additions & 0 deletions cloudinit/config/cc_ntp.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"opensuse-tumbleweed",
"opensuse-leap",
"photon",
"raspberry-pi-os",
"rhel",
"rocky",
"sle_hpc",
Expand Down Expand Up @@ -211,6 +212,11 @@
"confpath": "/etc/systemd/timesyncd.conf",
},
},
"raspberry-pi-os": {
"chrony": {
"confpath": "/etc/chrony/chrony.conf",
},
},
"rhel": {
"ntp": {
"service_name": "ntpd",
Expand Down
47 changes: 47 additions & 0 deletions cloudinit/config/cc_rpi_connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (C) 2024, Raspberry Pi Ltd.
#
# Author: Paul Oberosler <paul.oberosler@raspberrypi.com>
#
# This file is part of cloud-init. See LICENSE file for license information.

from cloudinit import subp
from cloudinit.cloud import Cloud
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema
from cloudinit.settings import PER_INSTANCE
import logging


LOG = logging.getLogger(__name__)
ENABLE_RPI_CONNECT_KEY = "enable_rpi_connect"

meta: MetaSchema = {
"id": "cc_rpi_connect",
"distros": ["raspberry-pi-os"],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": [ENABLE_RPI_CONNECT_KEY],
}


def configure_rpi_connect(enable: bool) -> None:
LOG.debug(f"Configuring rpi-connect: {enable}")

num = 0 if enable else 1

try:
subp.subp(["/usr/bin/raspi-config", "do_rpi_connect", str(num)])
except subp.ProcessExecutionError as e:
LOG.error("Failed to configure rpi-connect: %s", e)


def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if ENABLE_RPI_CONNECT_KEY in cfg:
# expect it to be a dictionary
enable = cfg[ENABLE_RPI_CONNECT_KEY]

if isinstance(enable, bool):
configure_rpi_connect(enable)
else:
LOG.warning(
"Invalid value for %s: %s", ENABLE_RPI_CONNECT_KEY, enable
)
Loading