Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
From c66a29f6f7b070bc653bce88e14fc5296e226768 Mon Sep 17 00:00:00 2001
From: Vince Perri <5596945+vinceaperri@users.noreply.github.com>
Date: Tue, 21 Apr 2026 22:46:42 +0000
Subject: [PATCH] ukify: fix insertion of padding in merged sections

Original patch:
https://github.com/systemd/systemd/commit/ec1d031f3de02f84beca89e2b402d085fba62be4

---
ukify | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/ukify b/ukify
index 7f9d6e6..232ebe4 100755
--- a/ukify
+++ b/ukify
@@ -618,9 +618,8 @@ def pe_add_sections(uki: UKI, output: str):
if new_section.Misc_VirtualSize > s.SizeOfRawData:
raise PEError(f'Not enough space in existing section {section.name} to append new data.')

- padding = bytes(new_section.SizeOfRawData - new_section.Misc_VirtualSize)
+ padding = bytes(s.SizeOfRawData - new_section.Misc_VirtualSize)
pe.__data__ = pe.__data__[:s.PointerToRawData] + data + padding + pe.__data__[pe.sections[i+1].PointerToRawData:]
- s.SizeOfRawData = new_section.SizeOfRawData
s.Misc_VirtualSize = new_section.Misc_VirtualSize
break
else:
--
2.45.4

8 changes: 8 additions & 0 deletions .github/workflows/tests-functional.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ jobs:
env:
HOST_ARCH: ${{ inputs.hostArch }}

- name: Patch ukify (AZL3)
if: inputs.hostDistro == 'azl3'
run: |
set -eux

UKIFY_PATH=$(which ukify)
sudo patch "$UKIFY_PATH" ./repo/.github/workflows/scripts/ukify-fix-insertion-of-padding-in-merged-sections.patch

- name: Install prerequisites (Ubuntu 24.04)
if: inputs.hostDistro == 'ubuntu2404'
run: |
Expand Down
22 changes: 11 additions & 11 deletions docs/imagecustomizer/api/distribution-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ APIs marked as **Preview** require the distribution's
| &emsp;[--output-image-file](./cli/customize.md#--output-image-filefile-path) | Yes | Yes | Preview |
| &emsp;[--output-path](./cli/customize.md#--output-pathfile-path) | Yes | Yes | Preview |
| &emsp;[--output-image-format](./cli/customize.md#--output-image-formatformat) | Yes | Yes | Preview |
| &emsp;&emsp;`baremetal-image` | Yes | No | Preview |
| &emsp;&emsp;`cosi` | Yes | No | Preview |
| &emsp;&emsp;`baremetal-image` | Yes | Yes | Preview |
| &emsp;&emsp;`cosi` | Yes | Yes | Preview |
| &emsp;&emsp;`iso` | Yes | No | No |
| &emsp;&emsp;`pxe-dir` | Yes | No | No |
| &emsp;&emsp;`pxe-tar` | Yes | No | No |
Expand All @@ -37,8 +37,8 @@ APIs marked as **Preview** require the distribution's
| &emsp;&emsp;`vhd-fixed` | Yes | Yes | Preview |
| &emsp;&emsp;`vhd` | Yes | Yes | Preview |
| &emsp;&emsp;`vhdx` | Yes | Yes | Preview |
| &emsp;[--cosi-compression-level](./cli/customize.md#--cosi-compression-levellevel) | Yes | No | No |
| &emsp;[--output-selinux-policy-path](./cli/customize.md#--output-selinux-policy-pathdirectory-path) | Yes | No | No |
| &emsp;[--cosi-compression-level](./cli/customize.md#--cosi-compression-levellevel) | Yes | Yes | No |
| &emsp;[--output-selinux-policy-path](./cli/customize.md#--output-selinux-policy-pathdirectory-path) | Yes | Yes | No |
| &emsp;[--config-file](./cli/customize.md#--config-filefile-path) | Yes | Yes | Preview |
| &emsp;[--rpm-source](./cli/customize.md#--rpm-sourcepath) | Yes | Yes | No |
| &emsp;[--disable-base-image-rpm-repos](./cli/customize.md#--disable-base-image-rpm-repos) | Yes | Yes | No |
Expand All @@ -53,11 +53,11 @@ APIs marked as **Preview** require the distribution's
| [input.image.path](./configuration/inputImage.md#path-string) | Yes | Yes | Preview |
| [input.image.oci](./configuration/inputImage.md#oci-ociimage) | Yes | Yes | No |
| [input.image.azureLinux](./configuration/inputImage.md#azurelinux-azurelinuximage) | Yes | Yes | N/A |
| [storage](./configuration/config.md#storage-storage) | Yes | No | No |
| [storage](./configuration/config.md#storage-storage) | Yes | Yes | No |
| [iso](./configuration/config.md#iso-iso) | Yes | No | No |
| [pxe](./configuration/config.md#pxe-pxe) | Yes | No | No |
| [os.hostname](./configuration/os.md#hostname-string) | Yes | Yes | Preview |
| [os.kernelCommandLine](./configuration/os.md#kernelcommandline-kernelcommandline) | Yes | No | No |
| [os.kernelCommandLine](./configuration/os.md#kernelcommandline-kernelcommandline) | Yes | Yes | No |
| [os.packages](./configuration/os.md#packages-packages) | Yes | Yes | Preview |
| &emsp;[.updateExistingPackages](./configuration/packages.md#updateexistingpackages-bool) | Yes | Yes | Preview |
| &emsp;[.installLists](./configuration/packages.md#installlists-string) | Yes | Yes | Preview |
Expand All @@ -74,12 +74,12 @@ APIs marked as **Preview** require the distribution's
| [os.modules](./configuration/os.md#modules-module) | Yes | Yes | Preview |
| [os.services](./configuration/os.md#services-services) | Yes | Yes | Preview |
| [os.overlays](./configuration/os.md#overlays-overlay) | Yes | Yes | No |
| [os.bootloader](./configuration/os.md#bootloader-bootloader) | Yes | No | No |
| [os.uki](./configuration/os.md#uki-uki) | Yes | No | No |
| [os.selinux](./configuration/os.md#selinux-selinux) | Yes | No | No |
| [os.bootloader](./configuration/os.md#bootloader-bootloader) | Yes | Yes | No |
| [os.uki](./configuration/os.md#uki-uki) | Yes | Yes | No |
| [os.selinux](./configuration/os.md#selinux-selinux) | Yes | Yes | No |
| [os.imageHistory](./configuration/os.md#imagehistory-string) | Yes | Yes | Preview |
| [scripts](./configuration/config.md#scripts-scripts) | Yes | Yes | Preview |
| [output.image](./configuration/output.md#image-outputimage) | Yes | Yes | Preview |
| [output.artifacts](./configuration/output.md#artifacts-outputartifacts) | Yes | No | No |
| [output.selinuxPolicyPath](./configuration/output.md#selinuxpolicypath-string) | Yes | No | No |
| [output.artifacts](./configuration/output.md#artifacts-outputartifacts) | Yes | Yes | No |
| [output.selinuxPolicyPath](./configuration/output.md#selinuxpolicypath-string) | Yes | Yes | No |
| [previewFeatures](./configuration/config.md#previewfeatures-string) | Yes | Yes | Yes |
18 changes: 10 additions & 8 deletions test/vmtests/vmtests/osmodifier/test_osmodifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ def setup_vm_with_osmodifier(
libvirt_conn: libvirt.virConnect,
session_close_list: List[Closeable],
) -> Tuple[SshClient, Path, Path]:
if distro_id == "azurelinux" and version_id == "4.0":
pytest.skip("Azure Linux 4.0 does not currently support this test.")

if distro_id == "azurelinux" and version_id == "4.0":
config_path = TEST_CONFIGS_DIR.joinpath("osmodifier-vm-config-azl4.yaml")
else:
Expand Down Expand Up @@ -283,16 +280,21 @@ def is_package_installed(ssh_client: SshClient, pkg_name: str, distro_id: str, v


def is_grub_bootloader(ssh_client: SshClient, distro_id: str, version_id: str) -> bool:
if is_package_installed(ssh_client, "grub2-efi-binary", distro_id, version_id) or is_package_installed(
ssh_client, "grub2-efi-binary-noprefix", distro_id, version_id
):
if distro_id == "azurelinux" and version_id == "4.0":
grub_packages = ["grub2-efi-x64", "grub2-efi-aa64"]
systemd_boot_pkgs = ["systemd-boot-unsigned", "systemd-boot"]
else:
grub_packages = ["grub2-efi-binary", "grub2-efi-binary-noprefix"]
systemd_boot_pkgs = ["systemd-boot"]

if any(is_package_installed(ssh_client, pkg, distro_id, version_id) for pkg in grub_packages):
return True

if is_package_installed(ssh_client, "systemd-boot", distro_id, version_id):
if any(is_package_installed(ssh_client, pkg, distro_id, version_id) for pkg in systemd_boot_pkgs):
return False

raise RuntimeError(
"Unknown bootloader: neither grub2-efi-binary, grub2-efi-binary-noprefix, nor systemd-boot is installed"
f"Unknown bootloader on {distro_id}-{version_id}: none of {grub_packages + systemd_boot_pkgs} is installed"
)


Expand Down
49 changes: 46 additions & 3 deletions toolkit/tools/imagegen/installutils/installutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package installutils

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -64,11 +65,19 @@ const (
// SELinuxConfigDisabled is the string value to set SELinux to disabled in the /etc/selinux/config file.
SELinuxConfigDisabled = "disabled"

// FedoraGrubCfgRelPath is the path to the grub config file on Fedora and Azure Linux systems,
// relative to the /boot directory.
FedoraGrubCfgRelPath = "grub2/grub.cfg"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we re-use the constants under customizeuki?


// DebianGrubCfgRelPath is the path to the grub config file on Debian and Ubuntu systems,
// relative to the /boot directory.
DebianGrubCfgRelPath = "grub/grub.cfg"

// FedoraGrubCfgFile is the filepath of the grub config file on Fedora and Azure Linux systems.
FedoraGrubCfgFile = "/boot/grub2/grub.cfg"
FedoraGrubCfgFile = "/boot/" + FedoraGrubCfgRelPath

// DebianGrubCfgFile is the filepath of the grub config file on Debian and Ubuntu systems.
DebianGrubCfgFile = "/boot/grub/grub.cfg"
DebianGrubCfgFile = "/boot/" + DebianGrubCfgRelPath

// FedoraGrubDir is the grub directory on Fedora and Azure Linux systems.
FedoraGrubDir = "/boot/grub2"
Expand Down Expand Up @@ -119,6 +128,27 @@ var (
}
)

// FindGrubCfgFile returns the bootDir-relative path to the first grub.cfg file that exists
// under bootDir. This should only be used in cases where we cannot determine the right DistroHandler ahead of time.
func FindGrubCfgFile(bootDir string) (string, error) {
grubCfgRelPaths := []string{
FedoraGrubCfgRelPath,
DebianGrubCfgRelPath,
}

for _, relPath := range grubCfgRelPaths {
absPath := filepath.Join(bootDir, relPath)
_, err := os.Stat(absPath)
if err == nil {
return absPath, nil
}
if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("failed to stat grub config file (%s):\n%w", absPath, err)
}
}
return "", fmt.Errorf("no grub config file found under (%s): %w", bootDir, os.ErrNotExist)
}

// CreateMountPointPartitionMap creates a map between the mountpoint supplied in the config file and the device path
// of the partition
// - partDevPathMap is a map of partition IDs to partition device paths
Expand Down Expand Up @@ -575,7 +605,13 @@ func installGrubTemplateFile(assetFile, targetFile, installRoot, rootDevice, boo
func CallGrubMkconfig(installChroot safechroot.ChrootInterface) (err error) {
ReportActionf("Running %s...", FedoraGrubMkconfigBinary)

// Force-disable os-prober. grub2-mkconfig is run inside an image-customization
// chroot that has the host's /dev bind-mounted; without this, /etc/grub.d/30_os-prober
// would enumerate the build host's disks and inject menuentries pointing at the
// host's kernels and partitions into the customized image's grub.cfg. Set via env
// so the protection applies regardless of /etc/default/grub state in the target image.
return shell.NewExecBuilder(FedoraGrubMkconfigBinary, "-o", FedoraGrubCfgFile).
EnvironmentVariables(append(os.Environ(), "GRUB_DISABLE_OS_PROBER=true")).
LogLevel(logrus.DebugLevel, logrus.DebugLevel).
Chroot(installChroot.ChrootDir()).
Execute()
Expand Down Expand Up @@ -786,7 +822,14 @@ func SELinuxRelabelFiles(installChroot safechroot.ChrootInterface, mountPointToF
selinuxType := strings.TrimSpace(stdout)
fileContextPath := fmt.Sprintf(fileContextBasePath, selinuxType)

targetRootPath := "/mnt/_bindmountroot"
// The bind-mount holder must live somewhere that:
// 1. is guaranteed writable inside the chroot, and
// 2. cannot be hidden by an entry in the image's /etc/fstab
// (e.g. an Azure data-disk placeholder mounted read-only at /mnt during customization).
// `/run` is added by safechroot as a fresh tmpfs (RW) on every chroot, and
// `getNonSpecialChrootMountPoints` already excludes it from the relabel set,
// so it is the safe choice.
targetRootPath := "/run/_bindmountroot"
targetRootFullPath := filepath.Join(installChroot.RootDir(), targetRootPath)

for _, mountToLabel := range listOfMountsToLabel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ func prepareImageConversionData(ctx context.Context, rawImageFile string, buildD
[]OsPackage, [randomization.UuidSize]byte, string, *CosiBootloader, []string, error,
) {
imageConnection, partitionsLayout, baseImageVerityMetadata, readonlyPartUuids, err := connectToExistingImage(
ctx, rawImageFile, buildDir, chrootDir, true, true, true, true)
ctx, rawImageFile, buildDir, chrootDir, true, true, true, true, nil)
if err != nil {
err = fmt.Errorf("%w:\n%w", ErrArtifactImageConnectionForExtraction, err)
return nil, nil, "", nil, [randomization.UuidSize]byte{}, "", nil, nil, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ func TestOutputAndInjectArtifacts(t *testing.T) {
if baseImageInfo.Version == baseImageVersionAzl2 {
t.Skip("'systemd-boot' is not available on Azure Linux 2.0")
}
if baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

ukifyExists, err := file.CommandExists("ukify")
assert.NoError(t, err)
Expand Down Expand Up @@ -141,9 +138,6 @@ func TestOutputAndInjectArtifactsCosi(t *testing.T) {
if baseImageInfo.Version == baseImageVersionAzl2 {
t.Skip("'systemd-boot' is not available on Azure Linux 2.0")
}
if baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

ukifyExists, err := file.CommandExists("ukify")
assert.NoError(t, err)
Expand Down
4 changes: 0 additions & 4 deletions toolkit/tools/pkg/imagecustomizerlib/baseconfigs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ func TestBaseConfigsFullRun(t *testing.T) {
if baseImageInfo.Version == baseImageVersionAzl2 {
t.Skip("'systemd-boot' is not available on Azure Linux 2.0")
}
if baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

ukifyExists, err := file.CommandExists("ukify")
assert.NoError(t, err)
if !ukifyExists {
Expand Down
12 changes: 0 additions & 12 deletions toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ func testConvertImageRawToCosi(t *testing.T, baseImageInfo testBaseImageInfo) {
if baseImageInfo.Distro == baseImageDistroAzureLinux && baseImageInfo.Version == baseImageVersionAzl2 {
t.Skip("'systemd-boot' is not available on Azure Linux 2.0")
}
if baseImageInfo.Distro == baseImageDistroAzureLinux && baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

ukifyExists, err := file.CommandExists("ukify")
assert.NoError(t, err)
if !ukifyExists {
Expand Down Expand Up @@ -178,10 +174,6 @@ func testConvertImageRawToCosiWithCompression(t *testing.T, baseImageInfo testBa
if baseImageInfo.Distro == baseImageDistroAzureLinux && baseImageInfo.Version == baseImageVersionAzl2 {
t.Skip("'systemd-boot' is not available on Azure Linux 2.0")
}
if baseImageInfo.Distro == baseImageDistroAzureLinux && baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

ukifyExists, err := file.CommandExists("ukify")
assert.NoError(t, err)
if !ukifyExists {
Expand Down Expand Up @@ -258,10 +250,6 @@ func TestConvertImageToBareMetalImage(t *testing.T) {

func testConvertImageToBareMetalImage(t *testing.T, baseImageInfo testBaseImageInfo) {
baseImage := checkSkipForCustomizeImage(t, baseImageInfo)
if baseImageInfo.Distro == baseImageDistroAzureLinux && baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

testTempDir := filepath.Join(tmpDir, fmt.Sprintf("TestConvertImageToBareMetalImage_%s", baseImageInfo.Name))
defer os.RemoveAll(testTempDir)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func CustomizeImageHelperCreate(ctx context.Context, rc *ResolvedConfig, tarFile
defer toolsChroot.Close(false)

imageConnection, partitionsLayout, _, _, err := connectToExistingImage(ctx, rc.RawImageFile, toolsChrootDir,
toolsRootImageDir, true, false, false, false)
toolsRootImageDir, true, false, false, false, distroHandler)
if err != nil {
return nil, "", err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,10 +585,6 @@ func TestCustomizeImagePackagesBadRepo(t *testing.T) {
func testCustomizeImagePackagesBadRepoHelper(t *testing.T, baseImageInfo testBaseImageInfo) {
baseImage := checkSkipForCustomizeImage(t, baseImageInfo)

if baseImageInfo.Version == baseImageVersionAzl4 {
t.Skip("Azure Linux 4.0 does not yet support this test")
}

testTmpDir := filepath.Join(tmpDir, fmt.Sprintf("TestCustomizeImagePackagesBadRepo_%s", baseImageInfo.Name))
defer os.RemoveAll(testTmpDir)

Expand Down
Loading
Loading