diff --git a/virt-v2v/BUILD.bazel b/virt-v2v/BUILD.bazel index 4eb4a4af5..c8e001e7b 100644 --- a/virt-v2v/BUILD.bazel +++ b/virt-v2v/BUILD.bazel @@ -1,4 +1,3 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") load( "@io_bazel_rules_docker//container:container.bzl", "container_image", @@ -90,34 +89,6 @@ container_image( visibility = ["//visibility:public"], ) -go_library( - name = "cold_lib", - srcs = [ - "customize-image.go", - "customize-rhel.go", - "embed-tool.go", - "entrypoint.go", - "xml-reader.go", - ], - embedsrcs = [ - "scripts/windows/9999-restore_config.ps1", - "scripts/windows/9999-restore_config_init.bat", - "scripts/windows/firstboot.bat", - "scripts/rhel/firstboot/README.md", - "scripts/rhel/run/README.md", - "scripts/rhel/run/network_config_util.sh", - "scripts/rhel/run/network_config_util_test", - ], - importpath = "github.com/konveyor/forklift-controller/virt-v2v", - visibility = ["//visibility:private"], -) - -go_binary( - name = "virt-v2v-wrapper", - embed = [":cold_lib"], - visibility = ["//visibility:public"], -) - container_image( name = "forklift-virt-v2v", base = ":virt-v2v-layer", @@ -129,7 +100,7 @@ container_image( "LIBGUESTFS_PATH": "/usr/lib64/guestfs/appliance", }, files = [ - ":virt-v2v-wrapper", + "//cmd:virt-v2v-wrapper", "@forklift//cmd/image-converter", "@forklift//cmd/virt-v2v-monitor", ], @@ -137,16 +108,6 @@ container_image( visibility = ["//visibility:public"], ) -go_test( - name = "cold_test", - srcs = [ - "cold_test.go", - "customize-rhel_test.go", - ], - data = glob(["testdata/**"]), - embed = [":cold_lib"], -) - rpmtree( name = "virt-v2v", rpms = [ @@ -502,13 +463,3 @@ rpmtree( ], visibility = ["//visibility:public"], ) - -go_test( - name = "virt-v2v_test", - srcs = [ - "cold_test.go", - "customize-rhel_test.go", - ], - data = glob(["testdata/**"]), - embed = [":virt-v2v_lib"], -) diff --git a/virt-v2v/cmd/BUILD.bazel b/virt-v2v/cmd/BUILD.bazel new file mode 100644 index 000000000..3ebde3a0e --- /dev/null +++ b/virt-v2v/cmd/BUILD.bazel @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") + +go_library( + name = "cmd_lib", + srcs = ["entrypoint.go"], + importpath = "github.com/konveyor/forklift-controller/virt-v2v/cmd", + visibility = ["//visibility:private"], + deps = [ + "//virt-v2v/pkg/customize", + "//virt-v2v/pkg/global", + "//virt-v2v/pkg/server", + "//virt-v2v/pkg/utils", + ], +) + +go_binary( + name = "virt-v2v-wrapper", + embed = [":cmd_lib"], + visibility = ["//visibility:public"], +) + +go_test( + name = "entrypoint_test", + srcs = ["entrypoint_test.go"], + embed = [":cmd_lib"], +) + +go_test( + name = "cmd_test", + srcs = ["entrypoint_test.go"], + embed = [":cmd_lib"], + deps = ["//virt-v2v/pkg/global"], +) diff --git a/virt-v2v/cmd/entrypoint.go b/virt-v2v/cmd/entrypoint.go new file mode 100644 index 000000000..b96b73ef5 --- /dev/null +++ b/virt-v2v/cmd/entrypoint.go @@ -0,0 +1,203 @@ +package main + +import ( + _ "embed" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/konveyor/forklift-controller/virt-v2v/pkg/customize" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/server" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils" +) + +func main() { + var err error + if _, found := os.LookupEnv("V2V_inPlace"); found { + err = convertVirtV2vInPlace() + } else { + err = convertVirtV2v() + } + + if err != nil { + fmt.Println("Error executing virt-v2v command ", err) + os.Exit(1) + } +} + +func convertVirtV2vInPlace() error { + args := []string{"-v", "-x", "-i", "libvirtxml"} + args = append(args, "--root") + if val, found := os.LookupEnv("V2V_RootDisk"); found { + args = append(args, val) + } else { + args = append(args, "first") + } + args = append(args, "/mnt/v2v/input.xml") + return executeVirtV2v("/usr/libexec/virt-v2v-in-place", args) +} + +func virtV2vBuildCommand() (args []string, err error) { + args = []string{"-v", "-x"} + source := os.Getenv("V2V_source") + + requiredEnvVars := map[string][]string{ + global.VSPHERE: {"V2V_libvirtURL", "V2V_secretKey", "V2V_vmName"}, + global.OVA: {"V2V_diskPath", "V2V_vmName"}, + } + + if envVars, ok := requiredEnvVars[source]; ok { + if !utils.CheckEnvVariablesSet(envVars...) { + return nil, fmt.Errorf("Following environment variables need to be defined: %v\n", envVars) + } + } else { + providers := make([]string, len(requiredEnvVars)) + for key := range requiredEnvVars { + providers = append(providers, key) + } + return nil, fmt.Errorf("virt-v2v supports the following providers: {%v}. Provided: %s\n", strings.Join(providers, ", "), source) + } + fmt.Println("Preparing virt-v2v") + + if err = utils.VirtV2VPrepEnvironment(); err != nil { + return + } + + args = append(args, "-o", "local", "-os", global.DIR) + + switch source { + case global.VSPHERE: + vsphereArgs, err := virtV2vVsphereArgs() + if err != nil { + return nil, err + } + args = append(args, vsphereArgs...) + case global.OVA: + args = append(args, "-i", "ova", os.Getenv("V2V_diskPath")) + } + + return args, nil +} + +func virtV2vVsphereArgs() (args []string, err error) { + args = append(args, "--root") + if utils.CheckEnvVariablesSet("V2V_RootDisk") { + args = append(args, os.Getenv("V2V_RootDisk")) + } else { + args = append(args, "first") + } + args = append(args, "-i", "libvirt", "-ic", os.Getenv("V2V_libvirtURL")) + args = append(args, "-ip", "/etc/secret/secretKey") + + if envStaticIPs := os.Getenv("V2V_staticIPs"); envStaticIPs != "" { + for _, macToIp := range strings.Split(envStaticIPs, "_") { + args = append(args, "--mac", macToIp) + } + } + + // Adds LUKS keys, if they exist + luksArgs, err := utils.AddLUKSKeys() + if err != nil { + return nil, fmt.Errorf("Error adding LUKS keys: %v", err) + } + args = append(args, luksArgs...) + + if info, err := os.Stat(global.VDDK); err == nil && info.IsDir() { + args = append(args, + "-it", "vddk", + "-io", fmt.Sprintf("vddk-libdir=%s", global.VDDK), + "-io", fmt.Sprintf("vddk-thumbprint=%s", os.Getenv("V2V_fingerprint")), + ) + } + var extraArgs []string + if envExtraArgs := os.Getenv("V2V_extra_args"); envExtraArgs != "" { + if err := json.Unmarshal([]byte(envExtraArgs), &extraArgs); err != nil { + return nil, fmt.Errorf("Error parsing extra arguments %v", err) + } + } + args = append(args, extraArgs...) + + args = append(args, "--", os.Getenv("V2V_vmName")) + return args, nil +} + +func convertVirtV2v() (err error) { + source := os.Getenv("V2V_source") + if source == global.VSPHERE { + if _, err := os.Stat("/etc/secret/cacert"); err == nil { + // use the specified certificate + err = os.Symlink("/etc/secret/cacert", "/opt/ca-bundle.crt") + if err != nil { + fmt.Println("Error creating ca cert link ", err) + os.Exit(1) + } + } else { + // otherwise, keep system pool certificates + err := os.Symlink("/etc/pki/tls/certs/ca-bundle.crt.bak", "/opt/ca-bundle.crt") + if err != nil { + fmt.Println("Error creating ca cert link ", err) + os.Exit(1) + } + } + } + + args, err := virtV2vBuildCommand() + if err != nil { + return + } + if err = executeVirtV2v("virt-v2v", args); err != nil { + return + } + + xmlFilePath, err := server.GetXMLFile(global.DIR, "xml") + if err != nil { + fmt.Println("Error getting XML file:", err) + return err + } + + err = customize.Run(source, xmlFilePath) + if err != nil { + fmt.Println("Error customizing the VM:", err) + return err + } + + return +} + +func executeVirtV2v(command string, args []string) error { + v2vCmd := exec.Command(command, args...) + monitorCmd := exec.Command("/usr/local/bin/virt-v2v-monitor") + monitorCmd.Stdout = os.Stdout + monitorCmd.Stderr = os.Stderr + + var writer *io.PipeWriter + monitorCmd.Stdin, writer = io.Pipe() + v2vCmd.Stdout = writer + v2vCmd.Stderr = writer + defer writer.Close() + + if err := monitorCmd.Start(); err != nil { + fmt.Printf("Error executing monitor command: %v\n", err) + return err + } + + fmt.Println("exec:", v2vCmd) + if err := v2vCmd.Run(); err != nil { + fmt.Printf("Error executing v2v command: %v\n", err) + return err + } + + // virt-v2v is done, we can close the pipe to virt-v2v-monitor + writer.Close() + + if err := monitorCmd.Wait(); err != nil { + fmt.Printf("Error waiting for virt-v2v-monitor to finish: %v\n", err) + return err + } + + return nil +} diff --git a/virt-v2v/cold_test.go b/virt-v2v/cmd/entrypoint_test.go similarity index 71% rename from virt-v2v/cold_test.go rename to virt-v2v/cmd/entrypoint_test.go index 581187162..7c497aa33 100644 --- a/virt-v2v/cold_test.go +++ b/virt-v2v/cmd/entrypoint_test.go @@ -3,34 +3,12 @@ package main import ( "strings" "testing" -) - -func TestGenName(t *testing.T) { - cases := []struct { - diskNum int - expected string - }{ - {1, "a"}, - {26, "z"}, - {27, "aa"}, - {28, "ab"}, - {52, "az"}, - {53, "ba"}, - {55, "bc"}, - {702, "zz"}, - {754, "abz"}, - } - for _, c := range cases { - got := genName(c.diskNum) - if got != c.expected { - t.Errorf("genName(%d) = %s; want %s", c.diskNum, got, c.expected) - } - } -} + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" +) func TestStaticIPs(t *testing.T) { - t.Setenv("V2V_source", VSPHERE) + t.Setenv("V2V_source", global.VSPHERE) t.Setenv("V2V_libvirtURL", "http://fake.com") t.Setenv("V2V_secretKey", "fake") t.Setenv("V2V_vmName", "test") diff --git a/virt-v2v/customize-image.go b/virt-v2v/customize-image.go deleted file mode 100644 index 5d12cff06..000000000 --- a/virt-v2v/customize-image.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "embed" - "fmt" - "os" - "os/exec" - "path/filepath" -) - -//go:embed scripts -var scriptFS embed.FS - -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, -// uploads them to the disk image using `virt-customize`. -// -// Arguments: -// - disks ([]string): The list of disk paths which should be customized -// -// Returns: -// - error: An error if something goes wrong during the process, or nil if successful. -func CustomizeWindows(disks []string, dir string, t FileSystemTool) error { - fmt.Printf("Customizing disks '%s'", disks) - err := t.CreateFilesFromFS(dir) - if err != nil { - return err - } - windowsScriptsPath := filepath.Join(dir, "scripts", "windows") - initPath := filepath.Join(windowsScriptsPath, "9999-restore_config_init.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) - - var extraArgs []string - extraArgs = append(extraArgs, getScriptArgs("upload", uploadScriptPath, uploadInitPath, uploadFirstbootPath)...) - extraArgs = append(extraArgs, getScriptArgs("add", disks...)...) - err = CustomizeDomainExec(extraArgs...) - if err != nil { - return err - } - return nil -} - -// getScriptArgs generates a list of arguments. -// -// Arguments: -// - argName (string): Argument name which should be used for all the values -// - values (...string): The list of values which should be joined with argument names. -// -// Returns: -// - []string: List of arguments -// -// Example: -// - getScriptArgs("firstboot", boot1, boot2) => ["--firstboot", boot1, "--firstboot", boot2] -func getScriptArgs(argName string, values ...string) []string { - var args []string - for _, val := range values { - args = append(args, fmt.Sprintf("--%s", argName), val) - } - return args -} - -// CustomizeDomainExec executes `virt-customize` to customize the image. -// -// Arguments: -// - extraArgs (...string): The additional arguments which will be appended to the `virt-customize` arguments. -// -// Returns: -// - error: An error if something goes wrong during the process, or nil if successful. -func CustomizeDomainExec(extraArgs ...string) error { - args := []string{"--verbose", "--format", "raw"} - args = append(args, extraArgs...) - - customizeCmd := exec.Command("virt-customize", args...) - customizeCmd.Stdout = os.Stdout - customizeCmd.Stderr = os.Stderr - - fmt.Println("exec:", customizeCmd) - if err := customizeCmd.Run(); err != nil { - return fmt.Errorf("error executing virt-customize command: %w", err) - } - return nil -} diff --git a/virt-v2v/entrypoint.go b/virt-v2v/entrypoint.go deleted file mode 100644 index b0751bf84..000000000 --- a/virt-v2v/entrypoint.go +++ /dev/null @@ -1,441 +0,0 @@ -package main - -import ( - "context" - _ "embed" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" -) - -const ( - OVA = "ova" - VSPHERE = "vSphere" - DIR = "/var/tmp/v2v" - FS = "/mnt/disks/disk[0-9]*" - BLOCK = "/dev/block[0-9]*" - VDDK = "/opt/vmware-vix-disklib-distrib" - LUKSDIR = "/etc/luks" -) - -var ( - xmlFilePath string - server *http.Server -) - -const LETTERS = "abcdefghijklmnopqrstuvwxyz" -const LETTERS_LENGTH = len(LETTERS) - -func main() { - var err error - if _, found := os.LookupEnv("V2V_inPlace"); found { - err = convertVirtV2vInPlace() - } else { - err = convertVirtV2v() - } - if err != nil { - fmt.Println("Error executing virt-v2v command ", err) - os.Exit(1) - } -} - -func getVmDiskPaths(domain *OvaVmconfig) []string { - var resp []string - for _, disk := range domain.Devices.Disks { - if disk.Source.File != "" { - resp = append(resp, disk.Source.File) - } - } - return resp -} - -func customizeVM(source string, xmlFilePath string) error { - domain, err := GetDomainFromXml(xmlFilePath) - if err != nil { - fmt.Printf("Error mapping xml to domain: %v\n", err) - - // No customization if we can't parse virt-v2v output. - return err - } - - // Get operating system. - operatingSystem := domain.Metadata.LibOsInfo.V2VOS.ID - if operatingSystem == "" { - fmt.Printf("Warning: no operating system found") - - // No customization when no known OS detected. - return nil - } else { - fmt.Printf("Operating System ID: %s\n", operatingSystem) - } - - // Get domain disks. - disks := getVmDiskPaths(domain) - if len(disks) == 0 { - fmt.Printf("Warning: no V2V domain disks found") - - // No customization when no disks found. - return nil - } else { - fmt.Printf("V2V domain disks: %v\n", disks) - } - - // Customization for vSphere source. - if source == VSPHERE { - // Windows - if strings.Contains(operatingSystem, "win") { - t := EmbedTool{filesystem: &scriptFS} - - err = CustomizeWindows(disks, DIR, &t) - if err != nil { - fmt.Println("Error customizing disk image:", err) - return err - } - } - - // Linux - if !strings.Contains(operatingSystem, "win") { - t := EmbedTool{filesystem: &scriptFS} - - err = CustomizeLinux(CustomizeDomainExec, disks, DIR, &t) - if err != nil { - fmt.Println("Error customizing disk image:", err) - return err - } - } - } - - return nil -} - -func convertVirtV2vInPlace() error { - args := []string{"-v", "-x", "-i", "libvirtxml"} - args = append(args, "--root") - if val, found := os.LookupEnv("V2V_RootDisk"); found { - args = append(args, val) - } else { - args = append(args, "first") - } - args = append(args, "/mnt/v2v/input.xml") - return executeVirtV2v("/usr/libexec/virt-v2v-in-place", args) -} - -// virtV2VPrepEnvironment prepares the filesystem of the virt-v2v container so -// that it will be in the state expected by the virt-v2v command. -func virtV2VPrepEnvironment() (err error) { - if err = os.MkdirAll(DIR, os.ModePerm); err != nil { - return fmt.Errorf("Error creating directory: %v", err) - } - - //Disks on filesystem storage. - if err = LinkDisks(FS, 15); err != nil { - return - } - //Disks on block storage. - if err = LinkDisks(BLOCK, 10); err != nil { - return - } - - return nil -} - -func virtV2vBuildCommand() (args []string, err error) { - args = []string{"-v", "-x"} - source := os.Getenv("V2V_source") - - requiredEnvVars := map[string][]string{ - VSPHERE: {"V2V_libvirtURL", "V2V_secretKey", "V2V_vmName"}, - OVA: {"V2V_diskPath", "V2V_vmName"}, - } - - if envVars, ok := requiredEnvVars[source]; ok { - if !checkEnvVariablesSet(envVars...) { - return nil, fmt.Errorf("Following environment variables need to be defined: %v\n", envVars) - } - } else { - providers := make([]string, len(requiredEnvVars)) - for key := range requiredEnvVars { - providers = append(providers, key) - } - return nil, fmt.Errorf("virt-v2v supports the following providers: {%v}. Provided: %s\n", strings.Join(providers, ", "), source) - } - fmt.Println("Preparing virt-v2v") - - if err = virtV2VPrepEnvironment(); err != nil { - return - } - - args = append(args, "-o", "local", "-os", DIR) - - switch source { - case VSPHERE: - vsphereArgs, err := virtV2vVsphereArgs() - if err != nil { - return nil, err - } - args = append(args, vsphereArgs...) - case OVA: - args = append(args, "-i", "ova", os.Getenv("V2V_diskPath")) - } - - return args, nil -} - -func virtV2vVsphereArgs() (args []string, err error) { - args = append(args, "--root") - if checkEnvVariablesSet("V2V_RootDisk") { - args = append(args, os.Getenv("V2V_RootDisk")) - } else { - args = append(args, "first") - } - args = append(args, "-i", "libvirt", "-ic", os.Getenv("V2V_libvirtURL")) - args = append(args, "-ip", "/etc/secret/secretKey") - - if envStaticIPs := os.Getenv("V2V_staticIPs"); envStaticIPs != "" { - for _, macToIp := range strings.Split(envStaticIPs, "_") { - args = append(args, "--mac", macToIp) - } - } - - // Adds LUKS keys, if they exist - luksArgs, err := addLUKSKeys() - if err != nil { - return nil, fmt.Errorf("Error adding LUKS keys: %v", err) - } - args = append(args, luksArgs...) - - if info, err := os.Stat(VDDK); err == nil && info.IsDir() { - args = append(args, - "-it", "vddk", - "-io", fmt.Sprintf("vddk-libdir=%s", VDDK), - "-io", fmt.Sprintf("vddk-thumbprint=%s", os.Getenv("V2V_fingerprint")), - ) - } - var extraArgs []string - if envExtraArgs := os.Getenv("V2V_extra_args"); envExtraArgs != "" { - if err := json.Unmarshal([]byte(envExtraArgs), &extraArgs); err != nil { - return nil, fmt.Errorf("Error parsing extra arguments %v", err) - } - } - args = append(args, extraArgs...) - - args = append(args, "--", os.Getenv("V2V_vmName")) - return args, nil -} - -// addLUKSKeys checks the LUKS directory for key files and returns the appropriate -// arguments for a 'virt-' command to add these keys. -// -// Returns a slice of strings representing the LUKS key arguments, or an error if -// there's an issue accessing the directory or reading the files. -func addLUKSKeys() ([]string, error) { - var luksArgs []string - - if _, err := os.Stat(LUKSDIR); err == nil { - files, err := getFilesInPath(LUKSDIR) - if err != nil { - return nil, fmt.Errorf("Error reading files in LUKS directory: %v", err) - } - - var luksFiles []string - for _, file := range files { - luksFiles = append(luksFiles, fmt.Sprintf("all:file:%s", file)) - } - - luksArgs = append(luksArgs, getScriptArgs("key", luksFiles...)...) - } else if !os.IsNotExist(err) { - return nil, fmt.Errorf("Error accessing the LUKS directory: %v", err) - } - - return luksArgs, nil -} - -func convertVirtV2v() (err error) { - source := os.Getenv("V2V_source") - if source == VSPHERE { - if _, err := os.Stat("/etc/secret/cacert"); err == nil { - // use the specified certificate - err = os.Symlink("/etc/secret/cacert", "/opt/ca-bundle.crt") - if err != nil { - fmt.Println("Error creating ca cert link ", err) - os.Exit(1) - } - } else { - // otherwise, keep system pool certificates - err := os.Symlink("/etc/pki/tls/certs/ca-bundle.crt.bak", "/opt/ca-bundle.crt") - if err != nil { - fmt.Println("Error creating ca cert link ", err) - os.Exit(1) - } - } - } - - args, err := virtV2vBuildCommand() - if err != nil { - return - } - if err = executeVirtV2v("virt-v2v", args); err != nil { - return - } - - if xmlFilePath, err = getXMLFile(DIR, "xml"); err != nil { - fmt.Println("Error getting XML file:", err) - return err - } - - err = customizeVM(source, xmlFilePath) - if err != nil { - fmt.Println("Error customizing the VM:", err) - return err - } - - http.HandleFunc("/ovf", ovfHandler) - http.HandleFunc("/shutdown", shutdownHandler) - server = &http.Server{Addr: ":8080"} - - fmt.Println("Starting server on :8080") - if err := server.ListenAndServe(); err != http.ErrServerClosed { - fmt.Printf("Error starting server: %v\n", err) - return err - } - - return -} - -func getFilesInPath(rootPath string) (paths []string, err error) { - files, err := os.ReadDir(rootPath) - if err != nil { - fmt.Println("Error reading the files in the directory ", err) - return - } - for _, file := range files { - if !file.IsDir() && !strings.HasPrefix(file.Name(), "..") { - paths = append(paths, fmt.Sprintf("%s/%s", rootPath, file.Name())) - } - } - return -} - -func checkEnvVariablesSet(envVars ...string) bool { - for _, v := range envVars { - if os.Getenv(v) == "" { - return false - } - } - return true -} - -func genName(diskNum int) string { - if diskNum <= 0 { - return "" - } - - index := (diskNum - 1) % LETTERS_LENGTH - cycles := (diskNum - 1) / LETTERS_LENGTH - - return genName(cycles) + string(LETTERS[index]) -} - -func LinkDisks(diskKind string, num int) (err error) { - disks, err := filepath.Glob(diskKind) - if err != nil { - fmt.Println("Error getting disks ", err) - return - } - - for _, disk := range disks { - diskNum, err := strconv.Atoi(disk[num:]) - if err != nil { - fmt.Println("Error getting disks names ", err) - return err - } - diskLink := fmt.Sprintf("%s/%s-sd%s", DIR, os.Getenv("V2V_vmName"), genName(diskNum+1)) - diskImgPath := disk - if diskKind == FS { - diskImgPath = fmt.Sprintf("%s/disk.img", disk) - } - if err = os.Symlink(diskImgPath, diskLink); err != nil { - fmt.Println("Error creating disk link ", err) - return err - } - } - return -} - -func executeVirtV2v(command string, args []string) error { - v2vCmd := exec.Command(command, args...) - monitorCmd := exec.Command("/usr/local/bin/virt-v2v-monitor") - monitorCmd.Stdout = os.Stdout - monitorCmd.Stderr = os.Stderr - - var writer *io.PipeWriter - monitorCmd.Stdin, writer = io.Pipe() - v2vCmd.Stdout = writer - v2vCmd.Stderr = writer - defer writer.Close() - - if err := monitorCmd.Start(); err != nil { - fmt.Printf("Error executing monitor command: %v\n", err) - return err - } - - fmt.Println("exec:", v2vCmd) - if err := v2vCmd.Run(); err != nil { - fmt.Printf("Error executing v2v command: %v\n", err) - return err - } - - // virt-v2v is done, we can close the pipe to virt-v2v-monitor - writer.Close() - - if err := monitorCmd.Wait(); err != nil { - fmt.Printf("Error waiting for virt-v2v-monitor to finish: %v\n", err) - return err - } - - return nil -} - -func getXMLFile(dir, fileExtension string) (string, error) { - files, err := filepath.Glob(filepath.Join(dir, "*."+fileExtension)) - if err != nil { - return "", err - } - if len(files) > 0 { - return files[0], nil - } - return "", fmt.Errorf("XML file was not found") -} - -func ovfHandler(w http.ResponseWriter, r *http.Request) { - xmlData, err := ReadXMLFile(xmlFilePath) - if err != nil { - fmt.Printf("Error: %v\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/xml") - _, err = w.Write(xmlData) - if err == nil { - w.WriteHeader(http.StatusOK) - } else { - fmt.Printf("Error writing response: %v\n", err) - http.Error(w, "Error writing response", http.StatusInternalServerError) - } - -} - -func shutdownHandler(w http.ResponseWriter, r *http.Request) { - fmt.Println("Shutdown request received. Shutting down server.") - w.WriteHeader(http.StatusNoContent) - if err := server.Shutdown(context.Background()); err != nil { - fmt.Printf("error shutting down server: %v\n", err) - } -} diff --git a/virt-v2v/pkg/customize/BUILD.bazel b/virt-v2v/pkg/customize/BUILD.bazel new file mode 100644 index 000000000..972307f18 --- /dev/null +++ b/virt-v2v/pkg/customize/BUILD.bazel @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "customize", + srcs = [ + "image.go", + "rhel.go", + "windows.go", + ], + embedsrcs = [ + "scripts/rhel/firstboot/README.md", + "scripts/rhel/run/README.md", + "scripts/rhel/run/network_config_util.sh", + "scripts/rhel/run/network_config_util_test", + "scripts/windows/9999-restore_config.ps1", + "scripts/windows/9999-restore_config_init.bat", + "scripts/windows/firstboot.bat", + ], + importpath = "github.com/konveyor/forklift-controller/virt-v2v/pkg/customize", + visibility = ["//visibility:public"], + deps = [ + "//virt-v2v/pkg/global", + "//virt-v2v/pkg/utils", + ], +) + +go_test( + name = "customize_test", + srcs = ["rhel_test.go"], + data = glob(["testdata/**"]), + embed = [":customize"], +) diff --git a/virt-v2v/pkg/customize/image.go b/virt-v2v/pkg/customize/image.go new file mode 100644 index 000000000..dd4e9a7d4 --- /dev/null +++ b/virt-v2v/pkg/customize/image.go @@ -0,0 +1,109 @@ +package customize + +import ( + "embed" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils" +) + +//go:embed scripts +var scriptFS embed.FS + +type FileSystemTool interface { + CreateFilesFromFS(dstDir string) error +} + +type DomainExecFunc func(args ...string) error + +func getVmDiskPaths(domain *utils.OvaVmconfig) []string { + var resp []string + for _, disk := range domain.Devices.Disks { + if disk.Source.File != "" { + resp = append(resp, disk.Source.File) + } + } + return resp +} + +func Run(source string, xmlFilePath string) error { + domain, err := utils.GetDomainFromXml(xmlFilePath) + if err != nil { + fmt.Printf("Error mapping xml to domain: %v\n", err) + + // No customization if we can't parse virt-v2v output. + return err + } + + // Get operating system. + operatingSystem := domain.Metadata.LibOsInfo.V2VOS.ID + if operatingSystem == "" { + fmt.Printf("Warning: no operating system found") + + // No customization when no known OS detected. + return nil + } else { + fmt.Printf("Operating System ID: %s\n", operatingSystem) + } + + // Get domain disks. + disks := getVmDiskPaths(domain) + if len(disks) == 0 { + fmt.Printf("Warning: no V2V domain disks found") + + // No customization when no disks found. + return nil + } else { + fmt.Printf("V2V domain disks: %v\n", disks) + } + + // TOOD: Test customzie with a OVA + // Customization for vSphere source. + if source == global.VSPHERE { + t := utils.EmbedTool{Filesystem: &scriptFS} + // windows + if strings.Contains(operatingSystem, "win") { + err = CustomizeWindows(CustomizeDomainExec, disks, global.DIR, &t) + if err != nil { + fmt.Println("Error customizing disk image:", err) + return err + } + } + + // Linux + if !strings.Contains(operatingSystem, "win") { + err = CustomizeLinux(CustomizeDomainExec, disks, global.DIR, &t) + if err != nil { + fmt.Println("Error customizing disk image:", err) + return err + } + } + } + return nil +} + +// CustomizeDomainExec executes `virt-customize` to customize the image. +// +// Arguments: +// - extraArgs (...string): The additional arguments which will be appended to the `virt-customize` arguments. +// +// Returns: +// - error: An error if something goes wrong during the process, or nil if successful. +func CustomizeDomainExec(extraArgs ...string) error { + args := []string{"--verbose", "--format", "raw"} + args = append(args, extraArgs...) + + customizeCmd := exec.Command("virt-customize", args...) + customizeCmd.Stdout = os.Stdout + customizeCmd.Stderr = os.Stderr + + fmt.Println("exec:", customizeCmd) + if err := customizeCmd.Run(); err != nil { + return fmt.Errorf("error executing virt-customize command: %w", err) + } + return nil +} diff --git a/virt-v2v/customize-rhel.go b/virt-v2v/pkg/customize/rhel.go similarity index 89% rename from virt-v2v/customize-rhel.go rename to virt-v2v/pkg/customize/rhel.go index 0b859a9fc..30baa2ead 100644 --- a/virt-v2v/customize-rhel.go +++ b/virt-v2v/pkg/customize/rhel.go @@ -1,17 +1,13 @@ -package main +package customize import ( "fmt" "os" "path/filepath" "strings" -) - -type FileSystemTool interface { - CreateFilesFromFS(dstDir string) error -} -type DomainExecFunc func(args ...string) error + "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) @@ -83,7 +79,7 @@ func addFirstbootScripts(extraArgs *[]string, dir string) error { return nil } - *extraArgs = append(*extraArgs, getScriptArgs("firstboot", firstBootScripts...)...) + *extraArgs = append(*extraArgs, utils.GetScriptArgs("firstboot", firstBootScripts...)...) return nil } @@ -101,7 +97,7 @@ func addRunScripts(extraArgs *[]string, dir string) error { return nil } - *extraArgs = append(*extraArgs, getScriptArgs("run", runScripts...)...) + *extraArgs = append(*extraArgs, utils.GetScriptArgs("run", runScripts...)...) return nil } @@ -125,12 +121,12 @@ func getScripts(directory string) ([]string, error) { // addDisksToCustomize appends disk arguments to extraArgs func addDisksToCustomize(extraArgs *[]string, disks []string) { - *extraArgs = append(*extraArgs, getScriptArgs("add", disks...)...) + *extraArgs = append(*extraArgs, utils.GetScriptArgs("add", disks...)...) } // addLuksKeysToCustomize appends key arguments to extraArgs func addLuksKeysToCustomize(extraArgs *[]string) error { - luksArgs, err := addLUKSKeys() + luksArgs, err := utils.AddLUKSKeys() if err != nil { return fmt.Errorf("error adding LUKS kyes: %w", err) } diff --git a/virt-v2v/customize-rhel_test.go b/virt-v2v/pkg/customize/rhel_test.go similarity index 99% rename from virt-v2v/customize-rhel_test.go rename to virt-v2v/pkg/customize/rhel_test.go index 805969d6f..20c4e63b2 100644 --- a/virt-v2v/customize-rhel_test.go +++ b/virt-v2v/pkg/customize/rhel_test.go @@ -1,4 +1,4 @@ -package main +package customize import ( "fmt" diff --git a/virt-v2v/scripts/rhel/firstboot/README.md b/virt-v2v/pkg/customize/scripts/rhel/firstboot/README.md similarity index 100% rename from virt-v2v/scripts/rhel/firstboot/README.md rename to virt-v2v/pkg/customize/scripts/rhel/firstboot/README.md diff --git a/virt-v2v/scripts/rhel/run/README.md b/virt-v2v/pkg/customize/scripts/rhel/run/README.md similarity index 100% rename from virt-v2v/scripts/rhel/run/README.md rename to virt-v2v/pkg/customize/scripts/rhel/run/README.md diff --git a/virt-v2v/scripts/rhel/run/network_config_util.sh b/virt-v2v/pkg/customize/scripts/rhel/run/network_config_util.sh similarity index 100% rename from virt-v2v/scripts/rhel/run/network_config_util.sh rename to virt-v2v/pkg/customize/scripts/rhel/run/network_config_util.sh diff --git a/virt-v2v/scripts/rhel/run/network_config_util_test b/virt-v2v/pkg/customize/scripts/rhel/run/network_config_util_test similarity index 100% rename from virt-v2v/scripts/rhel/run/network_config_util_test rename to virt-v2v/pkg/customize/scripts/rhel/run/network_config_util_test diff --git a/virt-v2v/scripts/windows/9999-restore_config.ps1 b/virt-v2v/pkg/customize/scripts/windows/9999-restore_config.ps1 similarity index 100% rename from virt-v2v/scripts/windows/9999-restore_config.ps1 rename to virt-v2v/pkg/customize/scripts/windows/9999-restore_config.ps1 diff --git a/virt-v2v/scripts/windows/9999-restore_config_init.bat b/virt-v2v/pkg/customize/scripts/windows/9999-restore_config_init.bat similarity index 100% rename from virt-v2v/scripts/windows/9999-restore_config_init.bat rename to virt-v2v/pkg/customize/scripts/windows/9999-restore_config_init.bat diff --git a/virt-v2v/scripts/windows/firstboot.bat b/virt-v2v/pkg/customize/scripts/windows/firstboot.bat similarity index 100% rename from virt-v2v/scripts/windows/firstboot.bat rename to virt-v2v/pkg/customize/scripts/windows/firstboot.bat diff --git a/virt-v2v/testdata/valid_config.xml b/virt-v2v/pkg/customize/testdata/valid_config.xml similarity index 100% rename from virt-v2v/testdata/valid_config.xml rename to virt-v2v/pkg/customize/testdata/valid_config.xml diff --git a/virt-v2v/pkg/customize/windows.go b/virt-v2v/pkg/customize/windows.go new file mode 100644 index 000000000..1e94da169 --- /dev/null +++ b/virt-v2v/pkg/customize/windows.go @@ -0,0 +1,49 @@ +package customize + +import ( + "fmt" + "path/filepath" + + "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, +// uploads them to the disk image using `virt-customize`. +// +// Arguments: +// - disks ([]string): The list of disk paths which should be customized +// +// 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 err + } + windowsScriptsPath := filepath.Join(dir, "scripts", "windows") + initPath := filepath.Join(windowsScriptsPath, "9999-restore_config_init.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) + + var extraArgs []string + extraArgs = append(extraArgs, utils.GetScriptArgs("upload", uploadScriptPath, uploadInitPath, uploadFirstbootPath)...) + extraArgs = append(extraArgs, utils.GetScriptArgs("add", disks...)...) + err = execFunc(extraArgs...) + if err != nil { + return err + } + return nil +} diff --git a/virt-v2v/pkg/global/BUILD.bazel b/virt-v2v/pkg/global/BUILD.bazel new file mode 100644 index 000000000..e88ec24b7 --- /dev/null +++ b/virt-v2v/pkg/global/BUILD.bazel @@ -0,0 +1,8 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "global", + srcs = ["variables.go"], + importpath = "github.com/konveyor/forklift-controller/virt-v2v/pkg/global", + visibility = ["//visibility:public"], +) diff --git a/virt-v2v/pkg/global/variables.go b/virt-v2v/pkg/global/variables.go new file mode 100644 index 000000000..a6581fe9a --- /dev/null +++ b/virt-v2v/pkg/global/variables.go @@ -0,0 +1,16 @@ +package global + +type MountPath string + +const ( + OVA = "ova" + VSPHERE = "vSphere" + DIR = "/var/tmp/v2v" + FS MountPath = "/mnt/disks/disk[0-9]*" + BLOCK MountPath = "/dev/block[0-9]*" + VDDK = "/opt/vmware-vix-disklib-distrib" + LUKSDIR = "/etc/luks" + + LETTERS = "abcdefghijklmnopqrstuvwxyz" + LETTERS_LENGTH = len(LETTERS) +) diff --git a/virt-v2v/pkg/server/BUILD.bazel b/virt-v2v/pkg/server/BUILD.bazel new file mode 100644 index 000000000..bc9387126 --- /dev/null +++ b/virt-v2v/pkg/server/BUILD.bazel @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "server", + srcs = ["server.go"], + importpath = "github.com/konveyor/forklift-controller/virt-v2v/pkg/server", + visibility = ["//visibility:public"], + deps = [ + "//virt-v2v/pkg/global", + "//virt-v2v/pkg/utils", + ], +) diff --git a/virt-v2v/pkg/server/server.go b/virt-v2v/pkg/server/server.go new file mode 100644 index 000000000..ae6a67601 --- /dev/null +++ b/virt-v2v/pkg/server/server.go @@ -0,0 +1,75 @@ +package server + +import ( + "context" + "errors" + "fmt" + "net/http" + "path/filepath" + + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" + "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils" +) + +var ( + server *http.Server +) + +// Start creates a webserver which is exposing information about the guest. +// The controller is periodically trying to request the server to get the information. +// This information is later used in the vm creation step such as the firmware for the OVA or +// Operating System for the VM creation. +func Start() error { + http.HandleFunc("/ovf", ovfHandler) + http.HandleFunc("/shutdown", shutdownHandler) + server = &http.Server{Addr: ":8080"} + + fmt.Println("Starting server on :8080") + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + fmt.Printf("Error starting server: %v\n", err) + return err + } + return nil +} + +func ovfHandler(w http.ResponseWriter, r *http.Request) { + xmlFilePath, err := GetXMLFile(global.DIR, "xml") + if err != nil { + fmt.Println("Error getting XML file:", err) + } + xmlData, err := utils.ReadXMLFile(xmlFilePath) + if err != nil { + fmt.Printf("Error: %v\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/xml") + _, err = w.Write(xmlData) + if err == nil { + w.WriteHeader(http.StatusOK) + } else { + fmt.Printf("Error writing response: %v\n", err) + http.Error(w, "Error writing response", http.StatusInternalServerError) + } + +} + +func shutdownHandler(w http.ResponseWriter, r *http.Request) { + fmt.Println("Shutdown request received. Shutting down server.") + w.WriteHeader(http.StatusNoContent) + if err := server.Shutdown(context.Background()); err != nil { + fmt.Printf("error shutting down server: %v\n", err) + } +} + +func GetXMLFile(dir, fileExtension string) (string, error) { + files, err := filepath.Glob(filepath.Join(dir, "*."+fileExtension)) + if err != nil { + return "", err + } + if len(files) > 0 { + return files[0], nil + } + return "", fmt.Errorf("XML file was not found") +} diff --git a/virt-v2v/pkg/utils/BUILD.bazel b/virt-v2v/pkg/utils/BUILD.bazel new file mode 100644 index 000000000..c203f26cc --- /dev/null +++ b/virt-v2v/pkg/utils/BUILD.bazel @@ -0,0 +1,19 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "utils", + srcs = [ + "command.go", + "embed-tool.go", + "xml-reader.go", + ], + importpath = "github.com/konveyor/forklift-controller/virt-v2v/pkg/utils", + visibility = ["//visibility:public"], + deps = ["//virt-v2v/pkg/global"], +) + +go_test( + name = "utils_test", + srcs = ["utils_test.go"], + embed = [":utils"], +) diff --git a/virt-v2v/pkg/utils/command.go b/virt-v2v/pkg/utils/command.go new file mode 100644 index 000000000..1d35871e1 --- /dev/null +++ b/virt-v2v/pkg/utils/command.go @@ -0,0 +1,156 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/konveyor/forklift-controller/virt-v2v/pkg/global" +) + +func CheckEnvVariablesSet(envVars ...string) bool { + for _, v := range envVars { + if os.Getenv(v) == "" { + return false + } + } + return true +} + +// GetScriptArgs generates a list of arguments. +// +// Arguments: +// - argName (string): Argument name which should be used for all the values +// - values (...string): The list of values which should be joined with argument names. +// +// Returns: +// - []string: List of arguments +// +// Example: +// - getScriptArgs("firstboot", boot1, boot2) => ["--firstboot", boot1, "--firstboot", boot2] +func GetScriptArgs(argName string, values ...string) []string { + var args []string + for _, val := range values { + args = append(args, fmt.Sprintf("--%s", argName), val) + } + return args +} + +// AddLUKSKeys checks the LUKS directory for key files and returns the appropriate +// arguments for a 'virt-' command to add these keys. +// +// Returns a slice of strings representing the LUKS key arguments, or an error if +// there's an issue accessing the directory or reading the files. +func AddLUKSKeys() ([]string, error) { + var luksArgs []string + + if _, err := os.Stat(global.LUKSDIR); err == nil { + files, err := GetFilesInPath(global.LUKSDIR) + if err != nil { + return nil, fmt.Errorf("Error reading files in LUKS directory: %v", err) + } + + var luksFiles []string + for _, file := range files { + luksFiles = append(luksFiles, fmt.Sprintf("all:file:%s", file)) + } + + luksArgs = append(luksArgs, GetScriptArgs("key", luksFiles...)...) + } else if !os.IsNotExist(err) { + return nil, fmt.Errorf("Error accessing the LUKS directory: %v", err) + } + + return luksArgs, nil +} + +func GetFilesInPath(rootPath string) (paths []string, err error) { + files, err := os.ReadDir(rootPath) + if err != nil { + fmt.Println("Error reading the files in the directory ", err) + return + } + for _, file := range files { + if !file.IsDir() && !strings.HasPrefix(file.Name(), "..") { + paths = append(paths, fmt.Sprintf("%s/%s", rootPath, file.Name())) + } + } + return +} + +// VirtV2VPrepEnvironment used in the cold migration. +// It creates a links between the downloaded guest image from virt-v2v and mounted PVC. +func VirtV2VPrepEnvironment() (err error) { + if err = os.MkdirAll(global.DIR, os.ModePerm); err != nil { + return fmt.Errorf("Error creating directory: %v", err) + } + + //Disks on Filesystem storage. + if err = LinkDisks(global.FS); err != nil { + return + } + //Disks on block storage. + if err = LinkDisks(global.BLOCK); err != nil { + return + } + return nil +} + +func genName(diskNum int) string { + if diskNum <= 0 { + return "" + } + + index := (diskNum - 1) % global.LETTERS_LENGTH + cycles := (diskNum - 1) / global.LETTERS_LENGTH + + return genName(cycles) + string(global.LETTERS[index]) +} + +func getDiskNumber(kind global.MountPath, disk string) (int, error) { + switch kind { + case global.FS: + return strconv.Atoi(disk[15:]) + case global.BLOCK: + return strconv.Atoi(disk[10:]) + default: + return 0, fmt.Errorf("wrong kind when specifying") + } +} + +func getDiskLink(kind global.MountPath, disk string) (string, error) { + diskNum, err := getDiskNumber(kind, disk) + if err != nil { + fmt.Println("Error getting disks names ", err) + return "", err + } + return filepath.Join( + global.DIR, + fmt.Sprintf("%s-sd%s", os.Getenv("V2V_vmName"), genName(diskNum+1)), + ), nil +} + +func LinkDisks(path global.MountPath) (err error) { + disks, err := filepath.Glob(string(path)) + if err != nil { + fmt.Println("Error getting disks ", err) + return + } + + for _, disk := range disks { + diskLink, err := getDiskLink(path, disk) + if err != nil { + fmt.Println("Error getting disks names ", err) + return err + } + if path == global.FS { + disk = fmt.Sprintf("%s/disk.img", disk) + } + if err = os.Symlink(disk, diskLink); err != nil { + fmt.Println("Error creating disk link ", err) + return err + } + } + return +} diff --git a/virt-v2v/embed-tool.go b/virt-v2v/pkg/utils/embed-tool.go similarity index 79% rename from virt-v2v/embed-tool.go rename to virt-v2v/pkg/utils/embed-tool.go index d82d38ae0..7b0afb43f 100644 --- a/virt-v2v/embed-tool.go +++ b/virt-v2v/pkg/utils/embed-tool.go @@ -1,4 +1,4 @@ -package main +package utils import ( "embed" @@ -8,12 +8,12 @@ import ( "path/filepath" ) -// EmbedTool for manipulating the embedded filesystem +// EmbedTool for manipulating the embedded Filesystem type EmbedTool struct { - filesystem *embed.FS + Filesystem *embed.FS } -// CreateFilesFromFS gets all files from the embedded filesystem and recreates them on the disk. +// CreateFilesFromFS gets all files from the embedded Filesystem and recreates them on the disk. // It creates all directories and keeps the hierarchy of the embedded files. // // Arguments: @@ -38,11 +38,11 @@ func (t *EmbedTool) CreateFilesFromFS(dstDir string) error { return nil } -// writeFileFromFS writes a file from the embedded filesystem to the disk. +// writeFileFromFS writes a file from the embedded Filesystem to the disk. // // Arguments: -// - src (string): The filepath from the embedded filesystem which should be writen to the disk. -// - dst (string): The destination path on the host filesystem to which the path should be writen. +// - src (string): The filepath from the embedded Filesystem which should be writen to the disk. +// - dst (string): The destination path on the host Filesystem to which the path should be writen. // // Returns: // - error: An error if the file cannot be read, or nil if successful. @@ -57,7 +57,7 @@ func (t *EmbedTool) writeFileFromFS(src, dst string) error { } } // Read the embedded file - srcData, err := t.filesystem.ReadFile(src) + srcData, err := t.Filesystem.ReadFile(src) if err != nil { fmt.Println("Error reading embedded file") return err @@ -70,14 +70,14 @@ func (t *EmbedTool) writeFileFromFS(src, dst string) error { return nil } -// getAllFilenames gets all files located inside the embedded filesystem. +// getAllFilenames gets all files located inside the embedded Filesystem. // Example of one path `scripts/windows/9999-restore_config_init.bat`. // // Returns: -// - []files: The file paths which are located inside the embedded filesystem. +// - []files: The file paths which are located inside the embedded Filesystem. // - error: An error if the file cannot be read, or nil if successful. func (t *EmbedTool) getAllFilenames() (files []string, err error) { - if err := fs.WalkDir(t.filesystem, ".", func(path string, d fs.DirEntry, err error) error { + if err := fs.WalkDir(t.Filesystem, ".", func(path string, d fs.DirEntry, err error) error { if d.IsDir() { return nil } diff --git a/virt-v2v/pkg/utils/utils_test.go b/virt-v2v/pkg/utils/utils_test.go new file mode 100644 index 000000000..e49a501e3 --- /dev/null +++ b/virt-v2v/pkg/utils/utils_test.go @@ -0,0 +1,29 @@ +package utils + +import ( + "testing" +) + +func TestGenName(t *testing.T) { + cases := []struct { + diskNum int + expected string + }{ + {1, "a"}, + {26, "z"}, + {27, "aa"}, + {28, "ab"}, + {52, "az"}, + {53, "ba"}, + {55, "bc"}, + {702, "zz"}, + {754, "abz"}, + } + + for _, c := range cases { + got := genName(c.diskNum) + if got != c.expected { + t.Errorf("genName(%d) = %s; want %s", c.diskNum, got, c.expected) + } + } +} diff --git a/virt-v2v/xml-reader.go b/virt-v2v/pkg/utils/xml-reader.go similarity index 99% rename from virt-v2v/xml-reader.go rename to virt-v2v/pkg/utils/xml-reader.go index 07aadc656..54aa804a7 100644 --- a/virt-v2v/xml-reader.go +++ b/virt-v2v/pkg/utils/xml-reader.go @@ -1,4 +1,4 @@ -package main +package utils import ( "encoding/xml"