From 650d73d2308d73fe596666a2f097aefda32845f6 Mon Sep 17 00:00:00 2001 From: Martin Necas Date: Wed, 4 Sep 2024 10:53:38 +0200 Subject: [PATCH] MTV-1388 | Add dynamic way to specify the virt-customize Issue: Right now all the run/firstboot scripts are located inside the conversion pod. To get new scripts to the users we need some alternative way to allow users to specify the scripts themselves. This will speed up the migration process as the users won't need to wait for the build and release of the patch. Additionally, the users themselves can tweak the scripts depending on their needs. Design: We have two options use the config maps, mount it to the container and read the mounted directory depending on their name we do virt-customize with them. An alternative solution would be to create CRD with all configurations such as `action`, `path` etc. I have chosen first as this is needed as soon as possible. User flow: 1. The user needs to create a config map inside the namespace where they want to migrate the VM. The config map needs to be named `mtv-virt-customize`. 2. Inside the configmap user can specify data with key/value definition. The value is script itself which they want to run. The key needs to follow regex `^([0-9]+_win_firstboot(([\w\-]*).ps1))$` or `^([0-9]+_linux_(run|firstboot)(([\w\-]*).sh))$` depending on where the customer wants the script to run. For example `00_win_firstboot_test.ps1` will specify that the data should be interpreted as PowerShell script which should start at boot. Alternatively, the Linux option has not only the `firstboot` but also the `run` option. This will be applied on the VM after virt-v2v conversion, but before the VM is started. Note: The number in the begining of the key sets the order. Conversion pod flow: 1. The `forklift-controller` checks if there is a config map inside the namespace. If the config map is present it is automatically mounted to the conversion pod at `/mnt/dynamic_scripts`. 2. The conversion pod checks if the `/mnt/dynamic_scripts` directory is present and if it is, it checks the files and their regexes. Depending on the VM operating system it will use different. From the filename the conversion pod determines the action required on the `virt-customize` step. Additional changes: - I have moved the static variables into a single file as we are using more and more and it's hard to keep track. - New batch file which will run all PowerShell scripts inside the windows first boot scripts dir. Signed-off-by: Martin Necas --- operator/config/manager/manager.yaml | 2 + .../forkliftcontroller/defaults/main.yml | 1 + .../controller/deployment-controller.yml.j2 | 4 + pkg/controller/plan/kubevirt.go | 49 +++++++++++- pkg/settings/migration.go | 8 ++ virt-v2v/pkg/customize/BUILD.bazel | 3 +- virt-v2v/pkg/customize/image.go | 1 + virt-v2v/pkg/customize/rhel.go | 75 ++++++++++--------- virt-v2v/pkg/customize/rhel_test.go | 32 ++++---- .../scripts/windows/9999-restore_config.ps1 | 9 +-- .../windows/9999-restore_config_init.bat | 3 - .../windows/9999-run-mtv-ps-scripts.bat | 14 ++++ virt-v2v/pkg/customize/utils.go | 56 ++++++++++++++ virt-v2v/pkg/customize/windows.go | 54 +++++++++---- virt-v2v/pkg/global/variables.go | 7 ++ virt-v2v/pkg/utils/embed-tool.go | 4 +- 16 files changed, 239 insertions(+), 83 deletions(-) delete mode 100644 virt-v2v/pkg/customize/scripts/windows/9999-restore_config_init.bat create mode 100644 virt-v2v/pkg/customize/scripts/windows/9999-run-mtv-ps-scripts.bat create mode 100644 virt-v2v/pkg/customize/utils.go diff --git a/operator/config/manager/manager.yaml b/operator/config/manager/manager.yaml index e81b2d655..c07a9806d 100644 --- a/operator/config/manager/manager.yaml +++ b/operator/config/manager/manager.yaml @@ -76,6 +76,8 @@ spec: value: ${OVIRT_OS_MAP} - name: VSPHERE_OS_MAP value: ${VSPHERE_OS_MAP} + - name: VIRT_CUSTOMIZE_MAP + value: ${VIRT_CUSTOMIZE_MAP} livenessProbe: httpGet: path: /healthz diff --git a/operator/roles/forkliftcontroller/defaults/main.yml b/operator/roles/forkliftcontroller/defaults/main.yml index abd45093f..25bbfa486 100644 --- a/operator/roles/forkliftcontroller/defaults/main.yml +++ b/operator/roles/forkliftcontroller/defaults/main.yml @@ -22,6 +22,7 @@ controller_configmap_name: "{{ controller_service_name }}-config" controller_service_name: "{{ app_name }}-controller" ovirt_osmap_configmap_name: "forklift-ovirt-osmap" vsphere_osmap_configmap_name: "forklift-vsphere-osmap" +virt_customize_configmap_name: "forklift-virt-customize" controller_deployment_name: "{{ controller_service_name }}" controller_container_name: "{{ app_name }}-controller" controller_container_limits_cpu: "500m" diff --git a/operator/roles/forkliftcontroller/templates/controller/deployment-controller.yml.j2 b/operator/roles/forkliftcontroller/templates/controller/deployment-controller.yml.j2 index 917102672..7082b2b8b 100644 --- a/operator/roles/forkliftcontroller/templates/controller/deployment-controller.yml.j2 +++ b/operator/roles/forkliftcontroller/templates/controller/deployment-controller.yml.j2 @@ -111,6 +111,10 @@ spec: - name: VSPHERE_OS_MAP value: {{ vsphere_osmap_configmap_name }} {% endif %} +{% if virt_customize_configmap_name is defined %} + - name: VIRT_CUSTOMIZE_MAP + value: {{ virt_customize_configmap_name }} +{% endif %} {% if controller_profile_kind is defined and controller_profile_path is defined and controller_profile_duration is defined %} - name: PROFILE_KIND value: "{{ controller_profile_kind }}" diff --git a/pkg/controller/plan/kubevirt.go b/pkg/controller/plan/kubevirt.go index e4e60c932..ac45c6954 100644 --- a/pkg/controller/plan/kubevirt.go +++ b/pkg/controller/plan/kubevirt.go @@ -68,8 +68,10 @@ const ( // DV deletion on completion AnnDeleteAfterCompletion = "cdi.kubevirt.io/storage.deleteAfterCompletion" // Max Length for vm name - NameMaxLength = 63 - VddkVolumeName = "vddk-vol-mount" + NameMaxLength = 63 + VddkVolumeName = "vddk-vol-mount" + DynamicScriptsVolumeName = "scripts-volume-mount" + DynamicScriptsMountPath = "/mnt/dynamic_scripts" ) // Labels @@ -1792,8 +1794,9 @@ func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, InitContainers: initContainers, Containers: []core.Container{ { - Name: "virt-v2v", - Env: environment, + ImagePullPolicy: core.PullAlways, + Name: "virt-v2v", + Env: environment, EnvFrom: []core.EnvFromSource{ { Prefix: "V2V_", @@ -1949,6 +1952,28 @@ func (r *KubeVirt) podVolumeMounts(vmVolumes []cnv.Volume, configMap *core.Confi } } + _, exists, err := r.findConfigMapInNamespace(Settings.VirtCustomizeConfigMap, r.Plan.Spec.TargetNamespace) + if err != nil { + err = liberr.Wrap(err) + return + } + if exists { + volumes = append(volumes, core.Volume{ + Name: DynamicScriptsVolumeName, + VolumeSource: core.VolumeSource{ + ConfigMap: &core.ConfigMapVolumeSource{ + LocalObjectReference: core.LocalObjectReference{ + Name: Settings.VirtCustomizeConfigMap, + }, + }, + }, + }) + mounts = append(mounts, core.VolumeMount{ + Name: DynamicScriptsVolumeName, + MountPath: DynamicScriptsMountPath, + }) + } + // Temporary space for VDDK library volumes = append(volumes, core.Volume{ Name: VddkVolumeName, @@ -2058,6 +2083,22 @@ func (r *KubeVirt) libvirtDomain(vmCr *VirtualMachine, pvcs []*core.PersistentVo return } +func (r *KubeVirt) findConfigMapInNamespace(name string, namespace string) (configMap *core.ConfigMap, exists bool, err error) { + configmap := &core.ConfigMap{} + err = r.Destination.Client.Get( + context.TODO(), + types.NamespacedName{Namespace: namespace, Name: name}, + configmap, + ) + if err != nil { + if k8serr.IsNotFound(err) { + return nil, false, nil + } + return nil, false, err + } + return configmap, true, nil +} + // Ensure the config map exists on the destination. func (r *KubeVirt) ensureConfigMap(vmRef ref.Ref) (configMap *core.ConfigMap, err error) { _, err = r.Source.Inventory.VM(&vmRef) diff --git a/pkg/settings/migration.go b/pkg/settings/migration.go index a32ed75b1..70413e400 100644 --- a/pkg/settings/migration.go +++ b/pkg/settings/migration.go @@ -26,6 +26,7 @@ const ( CleanupRetries = "CLEANUP_RETRIES" OvirtOsConfigMap = "OVIRT_OS_MAP" VsphereOsConfigMap = "VSPHERE_OS_MAP" + VirtCustomizeConfigMap = "VIRT_CUSTOMIZE_MAP" VddkJobActiveDeadline = "VDDK_JOB_ACTIVE_DEADLINE" VirtV2vExtraArgs = "VIRT_V2V_EXTRA_ARGS" VirtV2vExtraConfConfigMap = "VIRT_V2V_EXTRA_CONF_CONFIG_MAP" @@ -61,6 +62,8 @@ type Migration struct { OvirtOsConfigMap string // vSphere OS config map name VsphereOsConfigMap string + // vSphere OS config map name + VirtCustomizeConfigMap string // Active deadline for VDDK validation job VddkJobActiveDeadline int // Additional arguments for virt-v2v @@ -89,6 +92,11 @@ func (r *Migration) Load() (err error) { if r.SnapshotStatusCheckRate, err = getPositiveEnvLimit(SnapshotStatusCheckRate, 10); err != nil { return liberr.Wrap(err) } + if virtCustomizeConfigMap, ok := os.LookupEnv(VirtCustomizeConfigMap); ok { + r.VirtCustomizeConfigMap = virtCustomizeConfigMap + } else if Settings.Role.Has(MainRole) { + return liberr.Wrap(fmt.Errorf("failed to find environment variable %s", VirtCustomizeConfigMap)) + } if r.CleanupRetries, err = getPositiveEnvLimit(CleanupRetries, 10); err != nil { return liberr.Wrap(err) } diff --git a/virt-v2v/pkg/customize/BUILD.bazel b/virt-v2v/pkg/customize/BUILD.bazel index 37ecf9a12..ec8889d9b 100644 --- a/virt-v2v/pkg/customize/BUILD.bazel +++ b/virt-v2v/pkg/customize/BUILD.bazel @@ -6,13 +6,14 @@ go_library( "image.go", "rhel.go", "windows.go", + "utils.go", ], embedsrcs = [ "scripts/rhel/firstboot/README.md", "scripts/rhel/run/README.md", "scripts/rhel/run/network_config_util.sh", "scripts/windows/9999-restore_config.ps1", - "scripts/windows/9999-restore_config_init.bat", + "scripts/windows/9999-run-mtv-ps-scripts.bat", "scripts/windows/firstboot.bat", "scripts/rhel/run/ifcfg-double-quotes-test.d/expected-udev.rule", "scripts/rhel/run/ifcfg-double-quotes-test.d/root/etc/sysconfig/network-scripts/ifcfg-eth0", diff --git a/virt-v2v/pkg/customize/image.go b/virt-v2v/pkg/customize/image.go index 67b88e7fc..bb4cacf47 100644 --- a/virt-v2v/pkg/customize/image.go +++ b/virt-v2v/pkg/customize/image.go @@ -22,6 +22,7 @@ type DomainExecFunc func(args ...string) error func Run(disks []string, operatingSystem string) error { var err error + fmt.Printf("Customizing disks '%s'\n", disks) // Customization for vSphere source. t := utils.EmbedTool{Filesystem: &scriptFS} // windows diff --git a/virt-v2v/pkg/customize/rhel.go b/virt-v2v/pkg/customize/rhel.go index e076697ec..96076a96d 100644 --- a/virt-v2v/pkg/customize/rhel.go +++ b/virt-v2v/pkg/customize/rhel.go @@ -4,14 +4,14 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils" ) func CustomizeLinux(execFunc DomainExecFunc, disks []string, dir string, t FileSystemTool) error { - fmt.Printf("Customizing disks '%v'\n", disks) - var extraArgs []string // Step 1: Create files from the filesystem @@ -24,23 +24,31 @@ func CustomizeLinux(execFunc DomainExecFunc, disks []string, dir string, t FileS return err } - // Step 3: Add scripts - if err := addRunScripts(&extraArgs, dir); err != nil { + // Step 3: Add dynamic scripts from the configmap + if _, err := os.Stat(global.DYNAMIC_SCRIPTS_MOUNT_PATH); !os.IsNotExist(err) { + fmt.Println("Adding linux dynamic scripts") + if err = addRhelDynamicScripts(&extraArgs, global.DYNAMIC_SCRIPTS_MOUNT_PATH); err != nil { + return err + } + } + + // Step 4: Add scripts from embedded FS + if err := addRhelRunScripts(&extraArgs, dir); err != nil { return err } - if err := addFirstbootScripts(&extraArgs, dir); err != nil { + if err := addRhelFirstbootScripts(&extraArgs, dir); err != nil { return err } - // Step 4: Add the disks to customize + // Step 5: Add the disks to customize addDisksToCustomize(&extraArgs, disks) - // Step 5: Adds LUKS keys, if they exist + // Step 6: Adds LUKS keys, if they exist if err := addLuksKeysToCustomize(&extraArgs); err != nil { return err } - // Step 6: Execute the customization with the collected arguments + // Step 7: Execute the customization with the collected arguments if err := execFunc(extraArgs...); err != nil { return fmt.Errorf("failed to execute domain customization: %w", err) } @@ -65,11 +73,11 @@ func handleStaticIPConfiguration(extraArgs *[]string, dir string) error { return nil } -// addFirstbootScripts appends firstboot script arguments to extraArgs -func addFirstbootScripts(extraArgs *[]string, dir string) error { +// addRhelFirstbootScripts appends firstboot script arguments to extraArgs +func addRhelFirstbootScripts(extraArgs *[]string, dir string) error { firstbootScriptsPath := filepath.Join(dir, "scripts", "rhel", "firstboot") - firstBootScripts, err := getScripts(firstbootScriptsPath) + firstBootScripts, err := getScriptsWithSuffix(firstbootScriptsPath, global.SHELL_SUFFIX) if err != nil { return err } @@ -83,11 +91,11 @@ func addFirstbootScripts(extraArgs *[]string, dir string) error { return nil } -// addRunScripts appends run script arguments to extraArgs -func addRunScripts(extraArgs *[]string, dir string) error { +// addRhelRunScripts appends run script arguments to extraArgs +func addRhelRunScripts(extraArgs *[]string, dir string) error { runScriptsPath := filepath.Join(dir, "scripts", "rhel", "run") - runScripts, err := getScripts(runScriptsPath) + runScripts, err := getScriptsWithSuffix(runScriptsPath, global.SHELL_SUFFIX) if err != nil { return err } @@ -101,29 +109,6 @@ func addRunScripts(extraArgs *[]string, dir string) error { return nil } -// getScripts retrieves all .sh scripts from the specified directory -func getScripts(directory string) ([]string, error) { - files, err := os.ReadDir(directory) - if err != nil { - return nil, fmt.Errorf("failed to read firstboot scripts directory: %w", err) - } - - var scripts []string - for _, file := range files { - if !file.IsDir() && strings.HasSuffix(file.Name(), ".sh") && !strings.HasPrefix(file.Name(), "test-") { - scriptPath := filepath.Join(directory, file.Name()) - scripts = append(scripts, scriptPath) - } - } - - return scripts, nil -} - -// addDisksToCustomize appends disk arguments to extraArgs -func addDisksToCustomize(extraArgs *[]string, disks []string) { - *extraArgs = append(*extraArgs, utils.GetScriptArgs("add", disks...)...) -} - // addLuksKeysToCustomize appends key arguments to extraArgs func addLuksKeysToCustomize(extraArgs *[]string) error { luksArgs, err := utils.AddLUKSKeys() @@ -134,3 +119,19 @@ func addLuksKeysToCustomize(extraArgs *[]string) error { return nil } + +func addRhelDynamicScripts(extraArgs *[]string, dir string) error { + dynamicScripts, err := getScriptsWithRegex(dir, global.LINUX_DYNAMIC_REGEX) + if err != nil { + return err + } + for _, script := range dynamicScripts { + fmt.Printf("Adding linux dynamic scripts '%s'\n", script) + r := regexp.MustCompile(global.LINUX_DYNAMIC_REGEX) + groups := r.FindStringSubmatch(filepath.Base(script)) + // Option from the second regex group `(run|firstboot)` + action := groups[2] + *extraArgs = append(*extraArgs, utils.GetScriptArgs(action, script)...) + } + return nil +} diff --git a/virt-v2v/pkg/customize/rhel_test.go b/virt-v2v/pkg/customize/rhel_test.go index 20c4e63b2..5d18bc1c8 100644 --- a/virt-v2v/pkg/customize/rhel_test.go +++ b/virt-v2v/pkg/customize/rhel_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" ) type MockEmbedTool struct { @@ -125,9 +127,9 @@ func TestAddFirstbootScripts(t *testing.T) { } extraArgs := []string{} - err = addFirstbootScripts(&extraArgs, tempDir) + err = addRhelFirstbootScripts(&extraArgs, tempDir) if err != nil { - t.Fatalf("addFirstbootScripts returned an error: %v", err) + t.Fatalf("addRhelFirstbootScripts returned an error: %v", err) } if len(extraArgs) == 0 || !contains(extraArgs, "--firstboot") { @@ -148,9 +150,9 @@ func TestAddRunScripts(t *testing.T) { } extraArgs := []string{} - err = addRunScripts(&extraArgs, tempDir) + err = addRhelRunScripts(&extraArgs, tempDir) if err != nil { - t.Fatalf("addRunScripts returned an error: %v", err) + t.Fatalf("addRhelRunScripts returned an error: %v", err) } if len(extraArgs) == 0 || !contains(extraArgs, "--run") { @@ -170,9 +172,9 @@ func TestGetScripts(t *testing.T) { t.Fatalf("Error WriteFile: %v", err) } - scripts, err := getScripts(tempDir) + scripts, err := getScriptsWithSuffix(tempDir, global.SHELL_SUFFIX) if err != nil { - t.Fatalf("getScripts returned an error: %v", err) + t.Fatalf("getScriptsWithSuffix returned an error: %v", err) } expectedScripts := []string{ @@ -180,7 +182,7 @@ func TestGetScripts(t *testing.T) { filepath.Join(tempDir, "test2.sh"), } if !reflect.DeepEqual(scripts, expectedScripts) { - t.Fatalf("getScripts returned incorrect scripts: got %v, want %v", scripts, expectedScripts) + t.Fatalf("getScriptsWithSuffix returned incorrect scripts: got %v, want %v", scripts, expectedScripts) } } @@ -213,9 +215,9 @@ func TestAddFirstbootScripts_NoScripts(t *testing.T) { } extraArgs := []string{} - err = addFirstbootScripts(&extraArgs, tempDir) + err = addRhelFirstbootScripts(&extraArgs, tempDir) if err != nil { - t.Fatalf("addFirstbootScripts returned an error: %v", err) + t.Fatalf("addRhelFirstbootScripts returned an error: %v", err) } // Ensure no "--firstboot" argument is added when no scripts are found @@ -233,9 +235,9 @@ func TestAddRunScripts_NoScripts(t *testing.T) { } extraArgs := []string{} - err = addRunScripts(&extraArgs, tempDir) + err = addRhelRunScripts(&extraArgs, tempDir) if err != nil { - t.Fatalf("addRunScripts returned an error: %v", err) + t.Fatalf("addRhelRunScripts returned an error: %v", err) } // Ensure no "--run" argument is added when no scripts are found @@ -283,9 +285,9 @@ func TestAddFirstbootScripts_ReadDirFails(t *testing.T) { tempDir := "/invalid-dir" extraArgs := []string{} - err := addFirstbootScripts(&extraArgs, tempDir) + err := addRhelFirstbootScripts(&extraArgs, tempDir) if err == nil { - t.Fatalf("Expected error in addFirstbootScripts due to read failure, got nil") + t.Fatalf("Expected error in addRhelFirstbootScripts due to read failure, got nil") } } @@ -294,8 +296,8 @@ func TestAddRunScripts_ReadDirFails(t *testing.T) { tempDir := "/invalid-dir" extraArgs := []string{} - err := addRunScripts(&extraArgs, tempDir) + err := addRhelRunScripts(&extraArgs, tempDir) if err == nil { - t.Fatalf("Expected error in addRunScripts due to read failure, got nil") + t.Fatalf("Expected error in addRhelRunScripts due to read failure, got nil") } } diff --git a/virt-v2v/pkg/customize/scripts/windows/9999-restore_config.ps1 b/virt-v2v/pkg/customize/scripts/windows/9999-restore_config.ps1 index 9e89d58eb..45f4264b0 100644 --- a/virt-v2v/pkg/customize/scripts/windows/9999-restore_config.ps1 +++ b/virt-v2v/pkg/customize/scripts/windows/9999-restore_config.ps1 @@ -1,15 +1,10 @@ # Migration - Reconfigure disks # Initialize the log file created by the generated script -$logFile = $env:SystemDrive + '\Program Files\Guestfs\Firstboot\scripts-done\9999-restore_config.txt' -Write-Output ('Starting 9999-restore_config.ps1 script') > $logFile -Write-Output ('') >> $logFile -# script section to re-enable all offline drives # Re-enable all offline drives -Write-Output ('Re-enabling all offline drives') >> $logFile +Write-Host 'Re-enabling all offline drives' Get-Disk | Where { $_.FriendlyName -like '*VirtIO*' } | % { - Write-Output (' - ' + $_.Number + ': ' + $_.FriendlyName + '(' + [math]::Round($_.Size/1GB,2) + 'GB)') >> $logFile + Write-Host (' - ' + $_.Number + ': ' + $_.FriendlyName + '(' + [math]::Round($_.Size/1GB,2) + 'GB)') $_ | Set-Disk -IsOffline $false $_ | Set-Disk -IsReadOnly $false } -Write-Output ('') >> $logFile diff --git a/virt-v2v/pkg/customize/scripts/windows/9999-restore_config_init.bat b/virt-v2v/pkg/customize/scripts/windows/9999-restore_config_init.bat deleted file mode 100644 index 06208d4e0..000000000 --- a/virt-v2v/pkg/customize/scripts/windows/9999-restore_config_init.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -echo Restore configuration disks -PowerShell -NoProfile -ExecutionPolicy Bypass -Command "\'Program Files'\Guestfs\Firstboot\Scripts\9999-restore_config.ps1" diff --git a/virt-v2v/pkg/customize/scripts/windows/9999-run-mtv-ps-scripts.bat b/virt-v2v/pkg/customize/scripts/windows/9999-run-mtv-ps-scripts.bat new file mode 100644 index 000000000..c35c57dab --- /dev/null +++ b/virt-v2v/pkg/customize/scripts/windows/9999-run-mtv-ps-scripts.bat @@ -0,0 +1,14 @@ +@echo off + +set firstboot=C:\Program Files\Guestfs\Firstboot + +set scripts=%firstboot%\scripts +set scripts_done=%firstboot%\scripts-done +echo Running MTV first boot scripts +for %%f in ("%scripts%"\*.ps1) do ( + echo running "%%f" + PowerShell -NoProfile -ExecutionPolicy Bypass -File "%%~f" + set elvl=!errorlevel! + echo .... exit code !elvl! + move "%%f" "%scripts_done%" +) \ No newline at end of file diff --git a/virt-v2v/pkg/customize/utils.go b/virt-v2v/pkg/customize/utils.go new file mode 100644 index 000000000..6ae00cbe8 --- /dev/null +++ b/virt-v2v/pkg/customize/utils.go @@ -0,0 +1,56 @@ +package customize + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils" +) + +// getScriptsWithSuffix retrieves all scripts with suffix from the specified directory +func getScriptsWithSuffix(directory string, suffix string) ([]string, error) { + files, err := os.ReadDir(directory) + if err != nil { + return nil, fmt.Errorf("failed to read scripts directory: %w", err) + } + + var scripts []string + for _, file := range files { + if !file.IsDir() && strings.HasSuffix(file.Name(), suffix) && !strings.HasPrefix(file.Name(), "test-") { + scriptPath := filepath.Join(directory, file.Name()) + scripts = append(scripts, scriptPath) + } + } + + return scripts, nil +} + +// addDisksToCustomize appends disk arguments to extraArgs +func addDisksToCustomize(extraArgs *[]string, disks []string) { + *extraArgs = append(*extraArgs, utils.GetScriptArgs("add", disks...)...) +} + +func formatUpload(src string, dst string) string { + return fmt.Sprintf("%s:%s", src, dst) +} + +// getScriptsWithRegex retrieves all scripts with suffix from the specified directory +func getScriptsWithRegex(directory string, regex string) ([]string, error) { + files, err := os.ReadDir(directory) + if err != nil { + return nil, fmt.Errorf("failed to read scripts directory: %w", err) + } + + r := regexp.MustCompile(regex) + var scripts []string + for _, file := range files { + if !file.IsDir() && r.MatchString(file.Name()) { + scriptPath := filepath.Join(directory, file.Name()) + scripts = append(scripts, scriptPath) + } + } + return scripts, nil +} diff --git a/virt-v2v/pkg/customize/windows.go b/virt-v2v/pkg/customize/windows.go index 1e94da169..f1af89cfa 100644 --- a/virt-v2v/pkg/customize/windows.go +++ b/virt-v2v/pkg/customize/windows.go @@ -2,16 +2,13 @@ package customize import ( "fmt" + "os" "path/filepath" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils" ) -const ( - WIN_FIRSTBOOT_PATH = "/Program Files/Guestfs/Firstboot" - WIN_FIRSTBOOT_SCRIPTS_PATH = "/Program Files/Guestfs/Firstboot/scripts" -) - // CustomizeWindows customizes a windows disk image by uploading scripts. // // The function writes two bash scripts to the specified local tmp directory, @@ -23,27 +20,56 @@ const ( // Returns: // - error: An error if something goes wrong during the process, or nil if successful. func CustomizeWindows(execFunc DomainExecFunc, disks []string, dir string, t FileSystemTool) error { - fmt.Printf("Customizing disks '%s'", disks) err := t.CreateFilesFromFS(dir) + if err != nil { + return fmt.Errorf("failed to create files from filesystem: %w", err) + } + + var extraArgs []string + + if _, err = os.Stat(global.DYNAMIC_SCRIPTS_MOUNT_PATH); !os.IsNotExist(err) { + fmt.Println("Adding windows dynamic scripts") + err = addWinDynamicScripts(&extraArgs, global.DYNAMIC_SCRIPTS_MOUNT_PATH) + if err != nil { + return err + } + } + + addWinFirstbootScripts(&extraArgs, dir) + + addDisksToCustomize(&extraArgs, disks) + + err = execFunc(extraArgs...) if err != nil { return err } + return nil +} + +// addRhelFirstbootScripts appends firstboot script arguments to extraArgs +func addWinFirstbootScripts(extraArgs *[]string, dir string) { windowsScriptsPath := filepath.Join(dir, "scripts", "windows") - initPath := filepath.Join(windowsScriptsPath, "9999-restore_config_init.bat") + initPath := filepath.Join(windowsScriptsPath, "9999-run-mtv-ps-scripts.bat") restoreScriptPath := filepath.Join(windowsScriptsPath, "9999-restore_config.ps1") firstbootPath := filepath.Join(windowsScriptsPath, "firstboot.bat") // Upload scripts to the windows - uploadScriptPath := fmt.Sprintf("%s:%s", restoreScriptPath, WIN_FIRSTBOOT_SCRIPTS_PATH) - uploadInitPath := fmt.Sprintf("%s:%s", initPath, WIN_FIRSTBOOT_SCRIPTS_PATH) - uploadFirstbootPath := fmt.Sprintf("%s:%s", firstbootPath, WIN_FIRSTBOOT_PATH) + uploadScriptPath := formatUpload(restoreScriptPath, global.WIN_FIRSTBOOT_SCRIPTS_PATH) + uploadInitPath := formatUpload(initPath, global.WIN_FIRSTBOOT_SCRIPTS_PATH) + uploadFirstbootPath := formatUpload(firstbootPath, global.WIN_FIRSTBOOT_PATH) - var extraArgs []string - extraArgs = append(extraArgs, utils.GetScriptArgs("upload", uploadScriptPath, uploadInitPath, uploadFirstbootPath)...) - extraArgs = append(extraArgs, utils.GetScriptArgs("add", disks...)...) - err = execFunc(extraArgs...) + *extraArgs = append(*extraArgs, utils.GetScriptArgs("upload", uploadScriptPath, uploadInitPath, uploadFirstbootPath)...) +} + +func addWinDynamicScripts(extraArgs *[]string, dir string) error { + dynamicScripts, err := getScriptsWithRegex(dir, global.WINDOWS_DYNAMIC_REGEX) if err != nil { return err } + for _, script := range dynamicScripts { + fmt.Printf("Adding windows dynamic scripts '%s'\n", script) + upload := formatUpload(script, filepath.Join(global.WIN_FIRSTBOOT_SCRIPTS_PATH, filepath.Base(script))) + *extraArgs = append(*extraArgs, utils.GetScriptArgs("upload", upload)...) + } return nil } diff --git a/virt-v2v/pkg/global/variables.go b/virt-v2v/pkg/global/variables.go index 2f9d30ddd..8d9c589a2 100644 --- a/virt-v2v/pkg/global/variables.go +++ b/virt-v2v/pkg/global/variables.go @@ -12,6 +12,13 @@ const ( VDDK = "/opt/vmware-vix-disklib-distrib" LUKSDIR = "/etc/luks" + WIN_FIRSTBOOT_PATH = "/Program Files/Guestfs/Firstboot" + WIN_FIRSTBOOT_SCRIPTS_PATH = "/Program Files/Guestfs/Firstboot/scripts" + DYNAMIC_SCRIPTS_MOUNT_PATH = "/mnt/dynamic_scripts" + WINDOWS_DYNAMIC_REGEX = `^([0-9]+_win_firstboot(([\w\-]*).ps1))$` + LINUX_DYNAMIC_REGEX = `^([0-9]+_linux_(run|firstboot)(([\w\-]*).sh))$` + SHELL_SUFFIX = ".sh" + LETTERS = "abcdefghijklmnopqrstuvwxyz" LETTERS_LENGTH = len(LETTERS) ) diff --git a/virt-v2v/pkg/utils/embed-tool.go b/virt-v2v/pkg/utils/embed-tool.go index 7b0afb43f..4c02cc1e4 100644 --- a/virt-v2v/pkg/utils/embed-tool.go +++ b/virt-v2v/pkg/utils/embed-tool.go @@ -70,8 +70,8 @@ func (t *EmbedTool) writeFileFromFS(src, dst string) error { return nil } -// getAllFilenames gets all files located inside the embedded Filesystem. -// Example of one path `scripts/windows/9999-restore_config_init.bat`. +// getAllFilenames gets all files located inside the embedded filesystem. +// Example of one path `scripts/windows/9999-run-mtv-ps-scripts.bat`. // // Returns: // - []files: The file paths which are located inside the embedded Filesystem.