From 512da515e4c6b88e6f222e7e053d3b181a0d2dcf Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 2 Mar 2026 16:03:56 +1300 Subject: [PATCH 1/2] Use isoeditor library for ignition wrapping Replace custom CPIO archive generation with the isoeditor library's Archive() method to avoid code duplication and simplify the API. The WrapIgnition function now accepts an IgnitionContent object directly instead of performing path manipulation (truncating with Base() only to have the library re-extend the path). Assisted-by: Claude Code --- pkg/asset/appliance/appliance_liveiso.go | 8 +++- pkg/coreos/coreos.go | 50 ++++-------------------- 2 files changed, 14 insertions(+), 44 deletions(-) diff --git a/pkg/asset/appliance/appliance_liveiso.go b/pkg/asset/appliance/appliance_liveiso.go index d6e8bd33..8bae3535 100644 --- a/pkg/asset/appliance/appliance_liveiso.go +++ b/pkg/asset/appliance/appliance_liveiso.go @@ -26,7 +26,6 @@ const ( liveIsoWorkDir = "live-iso" liveIsoDataDir = "registry" bootstrapImageName = "/images/bootstrap-appliance.img" - bootstrapIgnitionPath = "/usr/lib/ignition/base.d/99-bootstrap.ign" defaultGrubConfigFilePath = "EFI/redhat/grub.cfg" defaultIsolinuxConfigFilePath = "isolinux/isolinux.cfg" defaultKargsConfigFilePath = "coreos/kargs.json" @@ -177,7 +176,12 @@ func (a *ApplianceLiveISO) buildLiveISO( return log.StopSpinner(spinner, err) } bootstrapImagePath := filepath.Join(workDir, bootstrapImageName) - if err := c.WrapIgnition(ignitionBytes, bootstrapIgnitionPath, bootstrapImagePath); err != nil { + ignitionContent := &isoeditor.IgnitionContent{ + SystemConfigs: map[string][]byte{ + "99-bootstrap.ign": ignitionBytes, + }, + } + if err := c.WrapIgnition(ignitionContent, bootstrapImagePath); err != nil { logrus.Errorf("Failed to create bootstrap image: %s", err.Error()) return log.StopSpinner(spinner, err) } diff --git a/pkg/coreos/coreos.go b/pkg/coreos/coreos.go index d40d2b88..2b2da2be 100644 --- a/pkg/coreos/coreos.go +++ b/pkg/coreos/coreos.go @@ -1,19 +1,18 @@ package coreos import ( - "bytes" - "compress/gzip" "encoding/json" "fmt" + "io" "os" "path/filepath" - "github.com/cavaliercoder/go-cpio" "github.com/cavaliergopher/grab/v3" "github.com/itchyny/gojq" "github.com/openshift/appliance/pkg/asset/config" "github.com/openshift/appliance/pkg/executer" "github.com/openshift/appliance/pkg/release" + "github.com/openshift/assisted-image-service/pkg/isoeditor" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -32,7 +31,7 @@ type CoreOS interface { DownloadDiskImage() (string, error) DownloadISO() (string, error) EmbedIgnition(ignition []byte, isoPath string) error - WrapIgnition(ignition []byte, ignitionPath, imagePath string) error + WrapIgnition(ignitionContent *isoeditor.IgnitionContent, imagePath string) error FetchCoreOSStream() (map[string]any, error) } @@ -121,7 +120,7 @@ func (c *coreos) EmbedIgnition(ignition []byte, isoPath string) error { return err } -func (c *coreos) WrapIgnition(ignition []byte, ignitionPath, imagePath string) error { +func (c *coreos) WrapIgnition(ignitionContent *isoeditor.IgnitionContent, imagePath string) error { ignitionImgFile, err := os.OpenFile(imagePath, os.O_CREATE|os.O_RDWR, 0664) if err != nil { return err @@ -132,12 +131,14 @@ func (c *coreos) WrapIgnition(ignition []byte, ignitionPath, imagePath string) e } }() - compressedCpio, err := generateCompressedCPIO(ignition, ignitionPath, 0o100_644) + // Generate compressed CPIO archive + archiveReader, err := ignitionContent.Archive() if err != nil { return err } - _, err = ignitionImgFile.Write(compressedCpio) + // Copy archive to the image file + _, err = io.Copy(ignitionImgFile, archiveReader) if err != nil { logrus.Errorf("Failed to write ignition data into %s: %s", ignitionImgFile.Name(), err.Error()) return err @@ -164,38 +165,3 @@ func (c *coreos) FetchCoreOSStream() (map[string]any, error) { return m, nil } - -func generateCompressedCPIO(fileContent []byte, filePath string, mode cpio.FileMode) ([]byte, error) { - // Run gzip compression - compressedBuffer := new(bytes.Buffer) - gzipWriter := gzip.NewWriter(compressedBuffer) - // Create CPIO archive - cpioWriter := cpio.NewWriter(gzipWriter) - - if err := cpioWriter.WriteHeader(&cpio.Header{ - Name: filePath, - Mode: mode, - Size: int64(len(fileContent)), - }); err != nil { - return nil, errors.Wrap(err, "Failed to write CPIO header") - } - if _, err := cpioWriter.Write(fileContent); err != nil { - return nil, errors.Wrap(err, "Failed to write CPIO archive") - } - - if err := cpioWriter.Close(); err != nil { - return nil, errors.Wrap(err, "Failed to close CPIO archive") - } - if err := gzipWriter.Close(); err != nil { - return nil, errors.Wrap(err, "Failed to gzip ignition config") - } - - padSize := (4 - (compressedBuffer.Len() % 4)) % 4 - for i := 0; i < padSize; i++ { - if err := compressedBuffer.WriteByte(0); err != nil { - return nil, err - } - } - - return compressedBuffer.Bytes(), nil -} From b2d5ead763164db9501fa798d2de79b6dd2a4e69 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 2 Mar 2026 17:04:04 +1300 Subject: [PATCH 2/2] Replace WrapIgnition with isoeditor.NewInitRamFSStreamReaderFromISO Remove WrapIgnition() and use the isoeditor library's NewInitRamFSStreamReaderFromISO() to append system ignition to the initrd file. This eliminates the need to create a separate bootstrap image and modify boot configuration files (grub.cfg, isolinux.cfg, kargs.json). Remove obsolete helper functions editFile() and fixKargsOffset() and their associated constants. Note that this will not work for s390 ISOs. The function NewIgnitionImageReader() is designed to work for all platforms, but it replaces the contents of the ignition embed area. For our purposes, we need to keep the system ignition out of this area so that the regular config.ign can be written to it in addition later. Assisted-by: Claude Code --- pkg/asset/appliance/appliance_liveiso.go | 110 ++++++----------------- pkg/coreos/coreos.go | 30 ------- 2 files changed, 27 insertions(+), 113 deletions(-) diff --git a/pkg/asset/appliance/appliance_liveiso.go b/pkg/asset/appliance/appliance_liveiso.go index 8bae3535..d4cbb2a2 100644 --- a/pkg/asset/appliance/appliance_liveiso.go +++ b/pkg/asset/appliance/appliance_liveiso.go @@ -3,9 +3,9 @@ package appliance import ( "encoding/json" "fmt" + "io" "os" "path/filepath" - "regexp" "github.com/openshift/appliance/pkg/asset/config" "github.com/openshift/appliance/pkg/asset/data" @@ -23,12 +23,8 @@ import ( ) const ( - liveIsoWorkDir = "live-iso" - liveIsoDataDir = "registry" - bootstrapImageName = "/images/bootstrap-appliance.img" - defaultGrubConfigFilePath = "EFI/redhat/grub.cfg" - defaultIsolinuxConfigFilePath = "isolinux/isolinux.cfg" - defaultKargsConfigFilePath = "coreos/kargs.json" + liveIsoWorkDir = "live-iso" + liveIsoDataDir = "registry" ) // ApplianceLiveISO is an asset that generates the OpenShift-based appliance. @@ -164,44 +160,45 @@ func (a *ApplianceLiveISO) buildLiveISO( ) spinner.FileToMonitor = consts.DeployIsoName - // Create bootstrap.img file - coreOSConfig := coreos.CoreOSConfig{ - ApplianceConfig: applianceConfig, - EnvConfig: envConfig, - } - c := coreos.NewCoreOS(coreOSConfig) + // Append bootstrap ignition to initrd using isoeditor library ignitionBytes, err := json.Marshal(recoveryIgnition.Bootstrap) if err != nil { logrus.Errorf("Failed to marshal recovery ignition to json: %s", err.Error()) return log.StopSpinner(spinner, err) } - bootstrapImagePath := filepath.Join(workDir, bootstrapImageName) ignitionContent := &isoeditor.IgnitionContent{ SystemConfigs: map[string][]byte{ "99-bootstrap.ign": ignitionBytes, }, } - if err := c.WrapIgnition(ignitionContent, bootstrapImagePath); err != nil { - logrus.Errorf("Failed to create bootstrap image: %s", err.Error()) + initrdReader, err := isoeditor.NewInitRamFSStreamReaderFromISO(coreosIsoPath, ignitionContent) + if err != nil { + logrus.Errorf("Failed to create initrd with bootstrap ignition: %s", err.Error()) return log.StopSpinner(spinner, err) } + defer func() { + if err := initrdReader.Close(); err != nil { + logrus.Errorf("Failed to close initrd reader: %s", err.Error()) + } + }() - // Add bootstrap.img to initrd - replacement := fmt.Sprintf("$1 $2 %s", bootstrapImageName) - grubCfgPath := filepath.Join(workDir, defaultGrubConfigFilePath) - if err := editFile(grubCfgPath, `(?m)^(\s+initrd) (.+| )+$`, replacement); err != nil { - return err + // Write the updated initrd to the extracted ISO + initrdPath := filepath.Join(workDir, "images/pxeboot/initrd.img") + initrdFile, err := os.Create(initrdPath) + if err != nil { + logrus.Errorf("Failed to create initrd file %s: %s", initrdPath, err.Error()) + return log.StopSpinner(spinner, err) } - replacement = fmt.Sprintf("${1},%s ${2}", bootstrapImageName) - isolinuxConfigFilePath := filepath.Join(workDir, defaultIsolinuxConfigFilePath) - if err := editFile(isolinuxConfigFilePath, `(?m)^(\s+append.*initrd=\S+) (.*)$`, replacement); err != nil { - return err + if _, err := io.Copy(initrdFile, initrdReader); err != nil { + if closeErr := initrdFile.Close(); closeErr != nil { + logrus.Errorf("Failed to close initrd file: %s", closeErr.Error()) + } + logrus.Errorf("Failed to write initrd file %s: %s", initrdPath, err.Error()) + return log.StopSpinner(spinner, err) } - - // Fix offset in kargs.json - initrdImageOffset := int64(len(bootstrapImageName) + 1) - if err := fixKargsOffset(workDir, defaultIsolinuxConfigFilePath, initrdImageOffset); err != nil { - return err + if err := initrdFile.Close(); err != nil { + logrus.Errorf("Failed to close initrd file: %s", err.Error()) + return log.StopSpinner(spinner, err) } // Generate live ISO @@ -222,56 +219,3 @@ func (a *ApplianceLiveISO) buildLiveISO( return log.StopSpinner(spinner, nil) } - -func editFile(fileName string, reString string, replacement string) error { - content, err := os.ReadFile(fileName) - if err != nil { - return err - } - - re := regexp.MustCompile(reString) - newContent := re.ReplaceAllString(string(content), replacement) - - if err := os.WriteFile(fileName, []byte(newContent), 0600); err != nil { - return err - } - - return nil -} - -func fixKargsOffset(workDir, configPath string, offset int64) error { - kargsConfigFilePath := filepath.Join(workDir, defaultKargsConfigFilePath) - kargsData, err := os.ReadFile(kargsConfigFilePath) - if err != nil { - return err - } - - var kargsConfig struct { - Default string `json:"default"` - Files []struct { - End string `json:"end"` - Offset int64 `json:"offset"` - Pad string `json:"pad"` - Path string `json:"path"` - } `json:"files"` - Size int64 `json:"size"` - } - if err := json.Unmarshal(kargsData, &kargsConfig); err != nil { - return err - } - for i, file := range kargsConfig.Files { - if file.Path == configPath { - kargsConfig.Files[i].Offset = file.Offset + offset - } - } - - workConfigFileContent, err := json.MarshalIndent(kargsConfig, "", " ") - if err != nil { - return err - } - if err := os.WriteFile(kargsConfigFilePath, workConfigFileContent, 0600); err != nil { - return err - } - - return nil -} diff --git a/pkg/coreos/coreos.go b/pkg/coreos/coreos.go index 2b2da2be..f91b9cb0 100644 --- a/pkg/coreos/coreos.go +++ b/pkg/coreos/coreos.go @@ -3,7 +3,6 @@ package coreos import ( "encoding/json" "fmt" - "io" "os" "path/filepath" @@ -12,7 +11,6 @@ import ( "github.com/openshift/appliance/pkg/asset/config" "github.com/openshift/appliance/pkg/executer" "github.com/openshift/appliance/pkg/release" - "github.com/openshift/assisted-image-service/pkg/isoeditor" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -31,7 +29,6 @@ type CoreOS interface { DownloadDiskImage() (string, error) DownloadISO() (string, error) EmbedIgnition(ignition []byte, isoPath string) error - WrapIgnition(ignitionContent *isoeditor.IgnitionContent, imagePath string) error FetchCoreOSStream() (map[string]any, error) } @@ -120,33 +117,6 @@ func (c *coreos) EmbedIgnition(ignition []byte, isoPath string) error { return err } -func (c *coreos) WrapIgnition(ignitionContent *isoeditor.IgnitionContent, imagePath string) error { - ignitionImgFile, err := os.OpenFile(imagePath, os.O_CREATE|os.O_RDWR, 0664) - if err != nil { - return err - } - defer func() { - if err := ignitionImgFile.Close(); err != nil { - logrus.Errorf("Failed to close ignition image file: %s", err.Error()) - } - }() - - // Generate compressed CPIO archive - archiveReader, err := ignitionContent.Archive() - if err != nil { - return err - } - - // Copy archive to the image file - _, err = io.Copy(ignitionImgFile, archiveReader) - if err != nil { - logrus.Errorf("Failed to write ignition data into %s: %s", ignitionImgFile.Name(), err.Error()) - return err - } - - return nil -} - func (c *coreos) FetchCoreOSStream() (map[string]any, error) { path, err := c.Release.ExtractFile(machineOsImageName, coreOsStream) if err != nil {