Skip to content
57 changes: 28 additions & 29 deletions toolkit/tools/imagecustomizer/docs/iso.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@

## Overview

Given a full disk image generated by the Azure Linux toolkit, the Azure Linux
Image Customizer can generate a LiveOS ISO image when the
`--output-image-format` is set to `iso`.

The LiveOS ISO image is a bootable image that boots into the rootfs of the
input full disk image. This eliminates the requirement to install that image
to persistent storage before booting it.

All customizations can still be made to the input full disk image rootfs as
usual using MIC, and will naturally carry over into the LiveOS ISO. This
includes customizations for kernel modules, dracut, and other early boot-time
actions.

While converting the input full disk image into a LiveOS ISO involves copying
The Azure Linux Image Customizer can customize an input image and package the
output as a LiveOS iso image. The input image can be a full disk image
(vhd/vhdx/qcow2/raw) or another LiveOS iso image. The LiveOS iso output format

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

does it have to be LiveOS iso only? what will happen if a regular iso is used?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Correct, the input iso has to an image previously generated by mic. If the input iso does not have the same exact names and location of bits as the one generated by mic, customization will fail.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[we need to locate bootloaders, kernel, initrd, rootfs, grub.cfg in specific locations and with specific names]

is specified by setting the `--output-image-format` command line parameter to
`iso`.

The LiveOS iso image is a bootable image that boots into a rootfs image
included on the iso media without the need to have anything pre-installed
on the target machine. The rootfs is kept read-only and an overlay in memory
is created so that processes can successfully write state.

Customizations are made to the input rootfs as usual using the Azure Linux
Image Customizer, and will naturally carry over into the LiveOS iso image.
This includes customizations for kernel modules, dracut, and other early
boot-time actions.

While converting the input full disk image into a LiveOS iso involves copying
almost all the artifacts unchanged - some artifacts are changed as follows:
- `grub.cfg` is modified by updating the kernel command-line arguments as
follows:
- the root is updated to the LiveOS root file system image.
- the LiveOS dracut parameters are appended.
- the MIC user-specified new parameters are appended.
- the user-specified new parameters are appended.
- SELinux is disabled.
- `/etc/fstab` is dropped from the rootfs as it typically conflicts with the
overlay setup required by the LiveOS.
Expand All @@ -31,16 +34,16 @@ almost all the artifacts unchanged - some artifacts are changed as follows:
dracut configuration that got used before will be re-used again when we are
re-generating the `initrd.img` and hence the original behavior is retained.

The current implementation for the LiveOS ISO does not support the following:
The current implementation for the LiveOS iso does not support the following:
- persistent storage.
- The machine loses all its state on reboot or shutdown.
- dm-verity.
- The ISO image cannot run dm-verity for the LiveOS partitions.
- The iso image cannot run dm-verity for the LiveOS partitions.
- disk layout.
- There is always one disk generated when an `iso` output format is
specified.
- SELinux
- No SELinux configuration is supported for the generated ISO image.
- No SELinux configuration is supported for the generated iso image.
- non-"Azure Linux toolkit" images
- images generated by tools other than the Azure Linux toolkit may fail to be
converted to a LiveOS iso. This is due to certain assumptions on the input
Expand Down Expand Up @@ -80,11 +83,6 @@ iso:
cloud-init-data/meta-data: /cloud-init-data/meta-data
kernelCommandLine:
ExtraCommandLine: "'ds=nocloud;s=file://run/initramfs/live/cloud-init-data'"
os:
users:
- name: test
password: testpassword
primaryGroup: sudo
```

#### Example 2
Expand All @@ -96,10 +94,6 @@ iso:
kernelCommandLine:
extraCommandLine: "'ds=nocloud;s=file://cloud-init-data'"
os:
users:
- name: test
password: testpassword
primaryGroup: sudo
additionalFiles:
cloud-init-data/user-data: /cloud-init-data/user-data
cloud-init-data/network-config: /cloud-init-data/network-config
Expand All @@ -111,15 +105,20 @@ os:
### Full Disk Image

The input full disk image must satisfy the following requirements in order for
MIC to be able to generate an iso image out of it:
the Azure Linux Image Cuztomizer to be able to generate an iso image out of it:
- File layout (after all partitions have been mounted):
- `/boot/grub2/grub.cfg` must exist and is the 'main' grub configuration (not
a redirection grub configuration file for example).
- The bootloader and the shim must exist under the `/boot` folder or any of
its subdirectories.
- For x64, `bootx64.efi` and `grubx64.efi` (or `grubx64-noprefix.efi`).
- For ARM64, `bootaa64.efi` and `grubaa64.efi` (or `grubx64-noprefix.efi`).
- For ARM64, `bootaa64.efi` and `grubaa64.efi` (or `grubaa64-noprefix.efi`).
- All grub configurations and related files must be stored under the `/boot`
folder. For example, grub.cfg cannot reference files outside that folder.
If it does, those referenced files will not be copied to the iso and may
cause grub to fail booting the desired operating system.

### LiveOS ISO Image

The input LiveOS iso image must be previously generated by the Azure Linux Image
Customizer.
28 changes: 17 additions & 11 deletions toolkit/tools/imagecustomizerapi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ import "fmt"
type Config struct {
Storage *Storage `yaml:"storage"`
Iso *Iso `yaml:"iso"`
OS OS `yaml:"os"`
Scripts Scripts `yaml:"scripts"`
OS *OS `yaml:"os"`
Scripts *Scripts `yaml:"scripts"`
}

func (c *Config) IsValid() (err error) {

hasStorage := false
if c.Storage != nil {
err = c.Storage.IsValid()
if err != nil {
return err
}
hasStorage = true
}

if c.Iso != nil {
Expand All @@ -27,19 +30,22 @@ func (c *Config) IsValid() (err error) {
}
}

err = c.OS.IsValid()
if err != nil {
return err
hasResetBootLoader := false
if c.OS != nil {
err = c.OS.IsValid()
if err != nil {
return err
}
hasResetBootLoader = c.OS.ResetBootLoaderType != ResetBootLoaderTypeDefault
}

err = c.Scripts.IsValid()
if err != nil {
return err
if c.Scripts != nil {
err = c.Scripts.IsValid()
if err != nil {
return err
}
}

hasStorage := c.Storage != nil
hasResetBootLoader := c.OS.ResetBootLoaderType != ResetBootLoaderTypeDefault

if hasStorage != hasResetBootLoader {
return fmt.Errorf("os.resetBootLoaderType and storage must be specified together")
}
Expand Down
24 changes: 12 additions & 12 deletions toolkit/tools/imagecustomizerapi/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestConfigIsValid(t *testing.T) {
},
},
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
},
Expand Down Expand Up @@ -67,7 +67,7 @@ func TestConfigIsValidLegacy(t *testing.T) {
},
},
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
},
Expand All @@ -91,7 +91,7 @@ func TestConfigIsValidNoBootType(t *testing.T) {
},
}},
},
OS: OS{
OS: &OS{
Hostname: "test",
ResetBootLoaderType: "hard-reset",
},
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestConfigIsValidMissingBootLoaderReset(t *testing.T) {
},
},
},
OS: OS{
OS: &OS{
Hostname: "test",
},
}
Expand All @@ -152,7 +152,7 @@ func TestConfigIsValidMultipleDisks(t *testing.T) {
},
BootType: "legacy",
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
},
Expand All @@ -169,7 +169,7 @@ func TestConfigIsValidZeroDisks(t *testing.T) {
BootType: BootTypeEfi,
Disks: []Disk{},
},
OS: OS{
OS: &OS{
Hostname: "test",
},
}
Expand All @@ -181,7 +181,7 @@ func TestConfigIsValidZeroDisks(t *testing.T) {

func TestConfigIsValidBadHostname(t *testing.T) {
config := &Config{
OS: OS{
OS: &OS{
Hostname: "test_",
},
}
Expand All @@ -200,7 +200,7 @@ func TestConfigIsValidBadDisk(t *testing.T) {
MaxSize: 0,
}},
},
OS: OS{
OS: &OS{
Hostname: "test",
},
}
Expand All @@ -220,7 +220,7 @@ func TestConfigIsValidMissingEsp(t *testing.T) {
}},
BootType: "efi",
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
},
Expand All @@ -242,7 +242,7 @@ func TestConfigIsValidMissingBiosBoot(t *testing.T) {
}},
BootType: "legacy",
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
},
Expand Down Expand Up @@ -279,7 +279,7 @@ func TestConfigIsValidInvalidMountPoint(t *testing.T) {
},
},
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
},
Expand Down Expand Up @@ -316,7 +316,7 @@ func TestConfigIsValidKernelCLI(t *testing.T) {
},
},
},
OS: OS{
OS: &OS{
ResetBootLoaderType: "hard-reset",
Hostname: "test",
KernelCommandLine: KernelCommandLine{
Expand Down
21 changes: 14 additions & 7 deletions toolkit/tools/pkg/imagecustomizerlib/customizeutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func doCustomizations(buildDir string, baseConfigPath string, config *imagecusto
return err
}

err = addRemoveAndUpdatePackages(buildDir, baseConfigPath, &config.OS, imageChroot, rpmsSources,
err = addRemoveAndUpdatePackages(buildDir, baseConfigPath, config.OS, imageChroot, rpmsSources,
Comment thread
gmileka marked this conversation as resolved.
useBaseImageRpmRepos)
if err != nil {
return err
Expand Down Expand Up @@ -112,9 +112,11 @@ func doCustomizations(buildDir string, baseConfigPath string, config *imagecusto
}
}

err = runScripts(baseConfigPath, config.Scripts.PostCustomization, imageChroot)
if err != nil {
return err
if config.Scripts != nil {
err = runScripts(baseConfigPath, config.Scripts.PostCustomization, imageChroot)
if err != nil {
return err
}
}

err = selinuxSetFiles(selinuxMode, imageChroot)
Expand All @@ -127,9 +129,11 @@ func doCustomizations(buildDir string, baseConfigPath string, config *imagecusto
return err
}

err = runScripts(baseConfigPath, config.Scripts.FinalizeCustomization, imageChroot)
if err != nil {
return err
if config.Scripts != nil {
err = runScripts(baseConfigPath, config.Scripts.FinalizeCustomization, imageChroot)
if err != nil {
return err
}
}

return nil
Expand Down Expand Up @@ -440,6 +444,9 @@ func handleBootLoader(baseConfigPath string, config *imagecustomizerapi.Config,
case imagecustomizerapi.ResetBootLoaderTypeHard:
logger.Log.Infof("Resetting bootloader config")

if config.Storage == nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I thought storage is not necessary to resetBootLoaderType but the other way around is mandated (where resetBootLoaderType is a must when storage is specified), is that not the case anymore?

return fmt.Errorf("failed to configure bootloader. Missing 'storage' configuration.")
}
// Hard-reset the grub config.
err := configureDiskBootLoader(imageConnection, config.Storage.FileSystems,
config.Storage.BootType, config.OS.SELinux, config.OS.KernelCommandLine, currentSelinuxMode)
Expand Down
Loading