From d21a092b3a21ae80bae199c2078cef527e2a8736 Mon Sep 17 00:00:00 2001 From: Amulyam24 Date: Wed, 10 Dec 2025 17:30:07 +0530 Subject: [PATCH] Add a feature to pass user provided files during qcow2ova image conversion --- cmd/image/qcow2ova/prep/prepare.go | 91 +++++++++++++++++++++++++++++- cmd/image/qcow2ova/qcow2ova.go | 7 ++- pkg/options.go | 2 + 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/cmd/image/qcow2ova/prep/prepare.go b/cmd/image/qcow2ova/prep/prepare.go index c4a9475d..e11eabfd 100644 --- a/cmd/image/qcow2ova/prep/prepare.go +++ b/cmd/image/qcow2ova/prep/prepare.go @@ -16,6 +16,7 @@ package prep import ( "fmt" + "io" "os" "path/filepath" "strings" @@ -34,7 +35,7 @@ var ( // - Install and configure multipath for rootfs // - Install all the required modules for PowerVM // - Sets the root password -func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error { +func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd, writeToDirPath string, writeFilesList []string) error { lo, err := setupLoop(volume) if err != nil { return err @@ -144,6 +145,19 @@ func prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error { return err } + // Write user provided files to given path in the image + imageWritePath := filepath.Join(mnt, writeToDirPath) + if err := os.MkdirAll(imageWritePath, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", imageWritePath, err) + } + + for _, filePath := range writeFilesList { + destinationPath := filepath.Join(imageWritePath, filepath.Base(filePath)) + if err := copyFiles(filePath, destinationPath); err != nil { + return err + } + } + err = Chroot(mnt) if err != nil { return err @@ -169,7 +183,7 @@ func UmountHostPartitions(mnt string) { } } -func Prepare4capture(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) error { +func Prepare4capture(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd, writeToDirPath string, writeFilesList []string) error { //cwd, err := os.Getwd() //if err != nil { // return err @@ -177,7 +191,7 @@ func Prepare4capture(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) e //defer os.Chdir(cwd) switch dist := strings.ToLower(dist); dist { case "rhel", "centos": - return prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd) + return prepare(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd, writeToDirPath, writeFilesList) case "coreos": klog.Info("No image preparation required for the coreos.") return nil @@ -185,3 +199,74 @@ func Prepare4capture(mnt, volume, dist, rhnuser, rhnpasswd, rootpasswd string) e return fmt.Errorf("not a supported distro: %s", dist) } } + +func copyFiles(src, dest string) error { + fileInfo, err := os.Stat(src) + if err != nil { + return err + } + + if fileInfo.IsDir() { + return copyDir(src, dest) + } + + return copyFile(src, dest) + +} + +func copyFile(src, dest string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + srcInfo, err := in.Stat() + if err != nil { + return err + } + + out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode()) + if err != nil { + return err + } + + if _, err := io.Copy(out, in); err != nil { + out.Close() + return err + } + + return out.Close() +} + +func copyDir(src, dest string) error { + srcInfo, err := os.Stat(src) + if err != nil { + return err + } + + if err := os.MkdirAll(dest, srcInfo.Mode()); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dest, err) + } + + entries, err := os.ReadDir(src) + if err != nil { + return err + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + destPath := filepath.Join(dest, entry.Name()) + + if entry.IsDir() { + if err := copyDir(srcPath, destPath); err != nil { + return err + } + } else { + if err := copyFile(srcPath, destPath); err != nil { + return err + } + } + } + return nil +} diff --git a/cmd/image/qcow2ova/qcow2ova.go b/cmd/image/qcow2ova/qcow2ova.go index 76abf9cd..bbc36cbe 100644 --- a/cmd/image/qcow2ova/qcow2ova.go +++ b/cmd/image/qcow2ova/qcow2ova.go @@ -60,6 +60,9 @@ Examples: # Step 2 - Make the necessary changes to the above generated template file(bash shell script) - image-prep.template # Step 3 - Run the qcow2ova with the modified image preparation template pvsadm image qcow2ova --image-name centos-82 --image-dist centos --image-url /root/CentOS-8-GenericCloud-8.2.2004-20200611.2.ppc64le.qcow2 --prep-template image-prep.template + + # For adding custom files to the image, run the qcow2ova with flags --write-files-list and --write-to-dir-path + pvsadm image qcow2ova --image-name --image-dist centos --image-url /root/CentOS-8-GenericCloud-8.2.2004-20200611.2.ppc64le.qcow2 --prep-template image-prep.template --write-files-list a.txt,b.log --write-to-dir-path /home/user # Customize the cloud config and Convert image with user defined cloud config template. # Step 1 - Dump the default cloud config template @@ -263,7 +266,7 @@ Qcow2 images location: klog.Info("Resize completed") klog.Info("Preparing the image") - err = prep.Prepare4capture(mnt, rawImg, opt.ImageDist, opt.RHNUser, opt.RHNPassword, opt.OSPassword) + err = prep.Prepare4capture(mnt, rawImg, opt.ImageDist, opt.RHNUser, opt.RHNPassword, opt.OSPassword, opt.WriteToDirPath, opt.WriteFilesList) if err != nil { return fmt.Errorf("failed while preparing the image for %s distro, err: %v", opt.ImageDist, err) } @@ -300,6 +303,8 @@ func init() { Cmd.Flags().StringVar(&pkg.ImageCMDOptions.OSPassword, "os-password", "", "Root user password, will auto-generate the 12 bits password(applicable only for redhat and cento distro)") Cmd.Flags().StringVarP(&pkg.ImageCMDOptions.TempDir, "temp-dir", "t", os.TempDir(), "Scratch space to use for OVA generation") Cmd.Flags().StringVar(&pkg.ImageCMDOptions.PrepTemplate, "prep-template", "", "Image preparation script template, use --prep-template-default to print the default template(supported distros: rhel and centos)") + Cmd.Flags().StringSliceVar(&pkg.ImageCMDOptions.WriteFilesList, "write-files-list", []string{}, "List of files to be copied to the image") + Cmd.Flags().StringVar(&pkg.ImageCMDOptions.WriteToDirPath, "write-to-dir-path", "", "User provided directory path where the provided files will be copied to") Cmd.Flags().BoolVar(&pkg.ImageCMDOptions.PrepTemplateDefault, "prep-template-default", false, "Prints the default image preparation script template, use --prep-template to set the custom template script(supported distros: rhel and centos)") Cmd.Flags().StringSliceVar(&pkg.ImageCMDOptions.PreflightSkip, "skip-preflight-checks", []string{}, "Skip the preflight checks(e.g: diskspace, platform, tools) - dev-only option") Cmd.Flags().BoolVar(&pkg.ImageCMDOptions.OSPasswordSkip, "skip-os-password", false, "Skip the root user password") diff --git a/pkg/options.go b/pkg/options.go index 49d3820a..ed981c8f 100644 --- a/pkg/options.go +++ b/pkg/options.go @@ -52,6 +52,8 @@ type imageCMDOptions struct { TempDir string PrepTemplate string PrepTemplateDefault bool + WriteFilesList []string + WriteToDirPath string CloudConfig string CloudConfigDefault bool OSPasswordSkip bool