Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0eb8f89
initial
elainezhao1 Sep 30, 2025
17d28a8
updates
elainezhao1 Oct 1, 2025
4c2f544
updates
elainezhao1 Oct 2, 2025
a6e3ccd
address some comments
elainezhao1 Oct 3, 2025
e90f0a7
rebase
elainezhao1 Oct 3, 2025
a80847b
rebase
elainezhao1 Oct 3, 2025
2e8bbdd
update schema
elainezhao1 Oct 3, 2025
2ba2a8f
address more feedback
elainezhao1 Oct 7, 2025
3e0aad7
Merge branch 'main' into user/elaine/poc1
elainezhao1 Oct 7, 2025
e10c7d1
update tests
elainezhao1 Oct 7, 2025
15e0bd6
Merge ValidateConfig and createImageCustomizerParameters.
cwize1 Oct 7, 2025
7a61336
Bug fix
cwize1 Oct 7, 2025
3017e68
Feedback updates
cwize1 Oct 8, 2025
ed28023
Split off ResolvedConfig from ImageCustomizerParameters.
cwize1 Oct 10, 2025
efc0bf2
resolve and rebase
elainezhao1 Oct 13, 2025
deb63e5
another rebase
elainezhao1 Oct 13, 2025
6ea0f26
Currently, the `ValidateConfig` and the `createResolvedConfig`
cwize1 Oct 13, 2025
89eaa04
another rebase
elainezhao1 Oct 14, 2025
4070cdb
another rebase
elainezhao1 Oct 15, 2025
9737ab1
another rebase
elainezhao1 Oct 15, 2025
f9dc60b
address comments
elainezhao1 Oct 15, 2025
a46a4b6
address more comments
elainezhao1 Oct 16, 2025
b1a4166
resolve comments
elainezhao1 Oct 17, 2025
7d8022e
add doc and rename test files
elainezhao1 Oct 17, 2025
344f754
add doc and rename test files
elainezhao1 Oct 17, 2025
b8895b7
update doc
elainezhao1 Oct 17, 2025
a6ea660
line wrap
elainezhao1 Oct 17, 2025
fcbee70
Merge branch 'main' into user/elaine/poc1
elainezhao1 Oct 17, 2025
2ba46bf
resolve more comments
elainezhao1 Oct 20, 2025
2bff675
Merge branch 'user/elaine/poc1' of github.com:microsoft/azure-linux-i…
elainezhao1 Oct 20, 2025
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
20 changes: 20 additions & 0 deletions toolkit/tools/imagecustomizerapi/baseconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package imagecustomizerapi

import (
"fmt"
"strings"
)

type BaseConfig struct {
Path string `yaml:"path" json:"path"`
}

func (b BaseConfig) IsValid() error {
if strings.TrimSpace(b.Path) == "" {
return fmt.Errorf("path must not be empty or whitespace")
}
return nil
}
13 changes: 13 additions & 0 deletions toolkit/tools/imagecustomizerapi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Config struct {
Scripts Scripts `yaml:"scripts" json:"scripts,omitempty"`
PreviewFeatures []PreviewFeature `yaml:"previewFeatures" json:"previewFeatures,omitempty"`
Output Output `yaml:"output" json:"output,omitempty"`
BaseConfigs []BaseConfig `yaml:"baseConfigs" json:"baseConfigs,omitempty"`
}

func (c *Config) IsValid() (err error) {
Expand Down Expand Up @@ -123,6 +124,18 @@ func (c *Config) IsValid() (err error) {
return fmt.Errorf("the 'reinitialize-verity' preview feature must be enabled to use 'storage.reinitializeVerity'")
}

if c.BaseConfigs != nil {
if !sliceutils.ContainsValue(c.PreviewFeatures, PreviewFeatureBaseConfigs) {
return fmt.Errorf("the '%s' preview feature must be enabled to use 'baseConfigs'", PreviewFeatureBaseConfigs)
}

for i, base := range c.BaseConfigs {
if err := base.IsValid(); err != nil {
return fmt.Errorf("invalid baseConfig item at index %d:\n%w", i, err)
}
}
}

return nil
}

Expand Down
5 changes: 4 additions & 1 deletion toolkit/tools/imagecustomizerapi/previewfeaturetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ const (

// PreviewFeatureFedora42 enables support for Fedora 42 images.
PreviewFeatureFedora42 PreviewFeature = "fedora-42"

// PreviewFeatureBaseConfigs enables support for base configuration.
PreviewFeatureBaseConfigs PreviewFeature = "base-configs"
)

func (pf PreviewFeature) IsValid() error {
switch pf {
case PreviewFeatureUki, PreviewFeatureOutputArtifacts, PreviewFeatureInjectFiles, PreviewFeaturePackageSnapshotTime,
PreviewFeatureKdumpBootFiles, PreviewFeatureFedora42:
PreviewFeatureKdumpBootFiles, PreviewFeatureFedora42, PreviewFeatureBaseConfigs:
return nil
default:
return fmt.Errorf("invalid preview feature: %s", pf)
Expand Down
15 changes: 15 additions & 0 deletions toolkit/tools/imagecustomizerapi/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@
"additionalProperties": false,
"type": "object"
},
"BaseConfig": {
"properties": {
"path": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"BootLoader": {
"properties": {
"resetType": {
Expand Down Expand Up @@ -82,6 +91,12 @@
},
"output": {
"$ref": "#/$defs/Output"
},
"baseConfigs": {
"items": {
"$ref": "#/$defs/BaseConfig"
},
"type": "array"
}
},
"additionalProperties": false,
Expand Down
130 changes: 14 additions & 116 deletions toolkit/tools/pkg/imagecreatorlib/imagecreator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
"fmt"
"os"
"path/filepath"
"strings"

"github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagecustomizerapi"
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagegen/diskutils"
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/file"
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/logger"
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/randomization"
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/safechroot"
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/pkg/imagecustomizerlib"
)
Expand All @@ -20,33 +16,6 @@
setupRoot = "/setuproot"
)

type ImageCreatorParameters struct {
// build dirs
buildDirAbs string

// configurations
configPath string
config *imagecustomizerapi.Config
rpmsSources []string
packageSnapshotTime string
toolsTar string

// output image
outputImageFormat imagecustomizerapi.ImageFormatType
outputImageFile string
outputImageDir string
outputImageBase string

// raw image file
rawImageFile string

imageUuid [randomization.UuidSize]byte
imageUuidStr string

partUuidToFstabEntry map[string]diskutils.FstabEntry
osRelease string
}

func CreateImageWithConfigFile(ctx context.Context, buildDir string, configFile string, rpmsSources []string,
toolsTar string, outputImageFile string, outputImageFormat string, distro string, distroVersion string,
packageSnapshotTime string,
Expand Down Expand Up @@ -78,20 +47,13 @@
rpmsSources []string, outputImageFile string, outputImageFormat string, toolsTar string, distro string,
distroVersion string, packageSnapshotTime string,
) error {
err := validateConfig(
rc, err := validateConfig(
ctx, baseConfigPath, &config, rpmsSources, toolsTar, outputImageFile,
outputImageFormat, packageSnapshotTime)
if err != nil {
return err
}

imageCreatorParameters, err := createImageCreatorParameters(
buildDir, baseConfigPath, &config, rpmsSources,
outputImageFormat, outputImageFile, packageSnapshotTime, toolsTar)
if err != nil {
return fmt.Errorf("invalid parameters:\n%w", err)
}

err = imagecustomizerlib.CheckEnvironmentVars()
if err != nil {
return err
Expand All @@ -100,119 +62,55 @@
imagecustomizerlib.LogVersionsOfToolDeps()

// ensure build and output folders are created up front
err = os.MkdirAll(imageCreatorParameters.buildDirAbs, os.ModePerm)
err = os.MkdirAll(rc.BuildDirAbs, os.ModePerm)
if err != nil {
return err
}

err = os.MkdirAll(imageCreatorParameters.outputImageDir, os.ModePerm)
outputImageDir := filepath.Dir(rc.OutputImageFile)
err = os.MkdirAll(outputImageDir, os.ModePerm)
if err != nil {
return err
}

disks := imageCreatorParameters.config.Storage.Disks
disks := rc.Config.Storage.Disks
diskConfig := disks[0]
installOSFunc := func(imageChroot *safechroot.Chroot) error {
return nil
}

logger.Log.Infof("Creating new image with parameters: %+v\n", imageCreatorParameters)
logger.Log.Infof("Creating new image with parameters: %+v\n", rc)

// Create distro config from distro name and version
distroHandler := imagecustomizerlib.NewDistroHandler(distro, distroVersion)

partIdToPartUuid, err := imagecustomizerlib.CreateNewImage(
distroHandler.GetTargetOs(), imageCreatorParameters.rawImageFile,
diskConfig, imageCreatorParameters.config.Storage.FileSystems,
imageCreatorParameters.buildDirAbs, setupRoot, installOSFunc)
distroHandler.GetTargetOs(), rc.RawImageFile,
diskConfig, rc.Config.Storage.FileSystems,
rc.BuildDirAbs, setupRoot, installOSFunc)
if err != nil {
return err
}

logger.Log.Debugf("Part id to part uuid map %v\n", partIdToPartUuid)
logger.Log.Infof("Image UUID: %s", imageCreatorParameters.imageUuidStr)
logger.Log.Infof("Image UUID: %s", rc.ImageUuidStr)

partUuidToFstabEntry, osRelease, err := imagecustomizerlib.CustomizeImageHelperImageCreator(
ctx, imageCreatorParameters.buildDirAbs, imageCreatorParameters.configPath,
imageCreatorParameters.config, imageCreatorParameters.rawImageFile,
imageCreatorParameters.rpmsSources, false, imageCreatorParameters.imageUuidStr,
imageCreatorParameters.packageSnapshotTime, imageCreatorParameters.toolsTar, distroHandler)
partUuidToFstabEntry, osRelease, err := imagecustomizerlib.CustomizeImageHelperImageCreator(ctx, rc, toolsTar,
distroHandler)
if err != nil {
return err
}

imageCreatorParameters.partUuidToFstabEntry = partUuidToFstabEntry
imageCreatorParameters.osRelease = osRelease
logger.Log.Debugf("Part uuid to fstab entry: %v\n", partUuidToFstabEntry)
logger.Log.Debugf("OsRelease: %v\n", osRelease)

logger.Log.Infof("Writing: %s", imageCreatorParameters.outputImageFile)
logger.Log.Infof("Writing: %s", rc.OutputImageFile)

err = imagecustomizerlib.ConvertImageFile(imageCreatorParameters.rawImageFile,
imageCreatorParameters.outputImageFile, imageCreatorParameters.outputImageFormat)
err = imagecustomizerlib.ConvertImageFile(rc.RawImageFile, rc.OutputImageFile, rc.OutputImageFormat)
if err != nil {
return err
}
logger.Log.Infof("Success!")

return nil
}

func createImageCreatorParameters(buildDir string, configPath string, config *imagecustomizerapi.Config,
rpmsSources []string, outputImageFormat string, outputImageFile string, packageSnapshotTime string,
toolsTar string,
) (*ImageCreatorParameters, error) {
ic := &ImageCreatorParameters{}

// working directories
buildDirAbs, err := filepath.Abs(buildDir)
if err != nil {
return nil, err
}

ic.buildDirAbs = buildDirAbs
ic.toolsTar = toolsTar

// intermediate writeable image
ic.rawImageFile = filepath.Join(buildDirAbs, imagecustomizerlib.BaseImageName)

// Create a uuid for the image
imageUuid, imageUuidStr, err := randomization.CreateUuid()
if err != nil {
return nil, err
}
ic.imageUuid = imageUuid
ic.imageUuidStr = imageUuidStr

// configuration
ic.configPath = configPath
ic.config = config

ic.rpmsSources = rpmsSources

err = imagecustomizerlib.ValidateRpmSources(rpmsSources)
if err != nil {
return nil, err
}

// output image
ic.outputImageFormat = imagecustomizerapi.ImageFormatType(outputImageFormat)
if err := ic.outputImageFormat.IsValid(); err != nil {
return nil, fmt.Errorf("invalid output image format:\n%w", err)
}

if ic.outputImageFormat == "" {
ic.outputImageFormat = config.Output.Image.Format
}

ic.outputImageFile = outputImageFile
if ic.outputImageFile == "" && config.Output.Image.Path != "" {
ic.outputImageFile = file.GetAbsPathWithBase(configPath, config.Output.Image.Path)
}

ic.outputImageBase = strings.TrimSuffix(filepath.Base(ic.outputImageFile), filepath.Ext(ic.outputImageFile))
ic.outputImageDir = filepath.Dir(ic.outputImageFile)
ic.packageSnapshotTime = packageSnapshotTime

return ic, nil
}
64 changes: 0 additions & 64 deletions toolkit/tools/pkg/imagecreatorlib/imagecreator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,67 +151,3 @@ func TestCreateImage_OutputImageFileAsRelativePath(t *testing.T) {
err = os.Remove(outputImageFileAbsolute)
assert.NoError(t, err)
}

func TestCreateImageCreatorParameters_OutputImageFileSelection(t *testing.T) {
checkSkipForCreateImage(t, runCreateImageTests)

testTmpDir := filepath.Join(tmpDir, "TestCreateImageCreatorParameters_OutputImageFileSelection")
defer os.RemoveAll(testTmpDir)

buildDir := filepath.Join(testTmpDir, "build")
outputImageFilePathAsArg := filepath.Join(testTmpDir, "image-as-arg.raw")
outputImageFilePathAsConfig := filepath.Join(testTmpDir, "image-as-config.raw")
toolsfile := filepath.Join(testTmpDir, "tools.tar.gz")

err := os.MkdirAll(buildDir, os.ModePerm)
assert.NoError(t, err)

configPath := "config.yaml"
config := &imagecustomizerapi.Config{}
rpmsSources := []string{}
outputImageFormat := "vhd"
outputImageFile := ""
packageSnapshotTime := ""

// The output image file is not specified in the config or as an argument, so the output image
// file will be empty.
ic, err := createImageCreatorParameters(buildDir, configPath, config,
rpmsSources, outputImageFormat, outputImageFile, packageSnapshotTime, toolsfile)
assert.NoError(t, err)
assert.Equal(t, ic.outputImageFile, "")

// Pass the output image file only in the config.
config.Output.Image.Path = outputImageFilePathAsConfig

// The output image file should be set to the value in the config.
ic, err = createImageCreatorParameters(buildDir, configPath, config,
rpmsSources, outputImageFormat, outputImageFile, packageSnapshotTime, toolsfile)
assert.NoError(t, err)
assert.Equal(t, ic.outputImageFile, outputImageFilePathAsConfig)
assert.Equal(t, ic.outputImageBase, "image-as-config")
assert.Equal(t, ic.outputImageDir, testTmpDir)
assert.Equal(t, ic.toolsTar, toolsfile)

// Pass the output image file only as an argument.
config.Output.Image.Path = ""
outputImageFile = outputImageFilePathAsArg

// The output image file should be set to the value passed as an argument.
ic, err = createImageCreatorParameters(buildDir, configPath, config,
rpmsSources, outputImageFormat, outputImageFile, packageSnapshotTime, toolsfile)
assert.NoError(t, err)
assert.Equal(t, ic.outputImageFile, outputImageFilePathAsArg)
assert.Equal(t, ic.outputImageBase, "image-as-arg")
assert.Equal(t, ic.outputImageDir, testTmpDir)

// Pass the output image file in both the config and as an argument.
config.Output.Image.Path = outputImageFilePathAsConfig

// The output image file should be set to the value passed as an argument.
ic, err = createImageCreatorParameters(buildDir, configPath, config,
rpmsSources, outputImageFormat, outputImageFile, packageSnapshotTime, toolsfile)
assert.NoError(t, err)
assert.Equal(t, ic.outputImageFile, outputImageFilePathAsArg)
assert.Equal(t, ic.outputImageBase, "image-as-arg")
assert.Equal(t, ic.outputImageDir, testTmpDir)
}
Loading
Loading