From 939a252e1969f5a74729969802a89839428a5666 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Tue, 25 Jun 2024 13:57:31 -0400 Subject: [PATCH] feat: check ovftool version Checks the minimum recommended version of VMware Open Virtualization Format Tool ('ovftool'). Ref: #135 Signed-off-by: Ryan Johnson --- .web-docs/components/builder/iso/README.md | 12 ++++ .web-docs/components/builder/vmx/README.md | 10 ++++ builder/vmware/common/driver.go | 69 ++++++++++++++++++---- builder/vmware/common/driver_esx5.go | 2 +- builder/vmware/common/step_export.go | 2 +- docs/builders/iso.mdx | 12 ++++ docs/builders/vmx.mdx | 10 ++++ 7 files changed, 105 insertions(+), 12 deletions(-) diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 0dde4b5f..0a6221e8 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -875,6 +875,18 @@ provisioner](/packer/docs/provisioner/file). +HCL Example: + +```hcl +source "vmware-iso" "example" { + # ... omitted for brevity ... + format = "ova" + ovftool_options = ["--compress=9", "--makeDeltaDisks"] + skip_export = false + keep_registered = false + skip_compaction = true +``` + ### Communicator configuration #### Optional common fields: diff --git a/.web-docs/components/builder/vmx/README.md b/.web-docs/components/builder/vmx/README.md index eef53cb5..af394273 100644 --- a/.web-docs/components/builder/vmx/README.md +++ b/.web-docs/components/builder/vmx/README.md @@ -421,6 +421,16 @@ boot time. +HCL Example: + +```hcl + format = "ova" + ovftool_options = ["--compress=9", "--makeDeltaDisks"] + skip_export = false + keep_registered = false + skip_compaction = true +``` + ### Output configuration #### Optional: diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 89415d1c..8f4a88a6 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -18,9 +18,17 @@ import ( "strings" "time" + "github.com/hashicorp/go-version" "github.com/hashicorp/packer-plugin-sdk/multistep" ) +// Sets the minimum recommended version of the VMware OVF Tool. +// https://developer.broadcom.com/tools/open-virtualization-format-ovf-tool/latest +const minimumRecommendOvfToolVersion = "4.6.0" + +// A regex to match the version of the VMware OVF Tool. +var ovfToolVersionRegex = regexp.MustCompile(`\d+\.\d+\.\d+`) + // A driver is able to talk to VMware, control virtual machines, etc. type Driver interface { // Clone clones the VMX and the disk to the destination path. The @@ -630,10 +638,10 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { return "", fmt.Errorf("unable to find host IP from devices %v, last error: %s", devices, lastError) } -func GetOVFTool() string { +func GetOvfTool() string { ovftool := "ovftool" if runtime.GOOS == "windows" { - ovftool = "ovftool.exe" + ovftool += ".exe" } if _, err := exec.LookPath(ovftool); err != nil { @@ -642,8 +650,47 @@ func GetOVFTool() string { return ovftool } +// CheckOvfToolVersion checks the version of the VMware OVF Tool. +func CheckOvfToolVersion(ovftoolPath string) (bool, error) { + output, err := exec.Command(ovftoolPath, "--version").CombinedOutput() + if err != nil { + log.Printf("[WARN] Error running 'ovftool --version': %v.", err) + log.Printf("[WARN] Returned: %s", string(output)) + return false, errors.New("failed to execute ovftool") + } + versionOutput := string(output) + log.Printf("Returned ovftool version: %s.", versionOutput) + + fields := strings.Fields(versionOutput) + for _, field := range fields { + if ovfToolVersionRegex.MatchString(field) { + currentVersion, err := version.NewVersion(field) + if err != nil { + log.Printf("[WARN] Failed to parse version '%s': %v.", field, err) + return false, errors.New("failed to parse version") + } + + minimumVersion, err := version.NewVersion(minimumRecommendOvfToolVersion) + if err != nil { + log.Printf("[WARN] Failed to parse minimum recommended version '%s': %v", minimumRecommendOvfToolVersion, err) + return false, errors.New("failed to parse minimum recommended version") + } + + if currentVersion.LessThan(minimumVersion) { + log.Printf("[WARN] The version of ovftool (%s) is below the minimum recommended version (%s).", currentVersion, minimumVersion) + return false, errors.New("ovftool version is below the minimum recommended version") + } + + return true, nil + } + } + + log.Printf("[WARN] Unable to determine the version of ovftool.") + return false, errors.New("unable to determine the version of ovftool") +} + func (d *VmwareDriver) Export(args []string) error { - ovftool := GetOVFTool() + ovftool := GetOvfTool() if ovftool == "" { return fmt.Errorf("error finding ovftool in path") } @@ -661,13 +708,15 @@ func (d *VmwareDriver) VerifyOvfTool(SkipExport, _ bool) error { } log.Printf("Verifying that ovftool exists...") - // Validate that tool exists, but no need to validate credentials. - ovftool := GetOVFTool() - if ovftool != "" { - return nil - } else { - return fmt.Errorf("ovftool not found in path. either set " + - "'skip_export = true', remove 'format' option, or install ovftool") + ovftoolPath := GetOvfTool() + if ovftoolPath == "" { + return fmt.Errorf("ovftool not found; install and include it in your PATH") } + log.Printf("Checking ovftool version...") + if !CheckOvfToolVersion(ovftoolPath) { + log.Printf("[WARN] ovftool does not meet the minimum recommended version: %s", minimumRecommendOvfToolVersion) + } + + return nil } diff --git a/builder/vmware/common/driver_esx5.go b/builder/vmware/common/driver_esx5.go index 7661bc23..8cce8a73 100644 --- a/builder/vmware/common/driver_esx5.go +++ b/builder/vmware/common/driver_esx5.go @@ -355,7 +355,7 @@ func (d *ESX5Driver) VerifyOvfTool(SkipExport, skipValidateCredentials bool) err // check that password is valid by sending a dummy ovftool command // now, so that we don't fail for a simple mistake after a long // build - ovftool := GetOVFTool() + ovftool := GetOvfTool() if d.Password == "" { return fmt.Errorf("exporting the vm from esxi with ovftool requires " + diff --git a/builder/vmware/common/step_export.go b/builder/vmware/common/step_export.go index 8b97daed..b2a9fa1e 100644 --- a/builder/vmware/common/step_export.go +++ b/builder/vmware/common/step_export.go @@ -93,7 +93,7 @@ func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multiste var args, ui_args []string - ovftool := GetOVFTool() + ovftool := GetOvfTool() if c.RemoteType == "esx5" { // Generate arguments for the ovftool command, but obfuscating the // password that we can log the command to the UI for debugging. diff --git a/docs/builders/iso.mdx b/docs/builders/iso.mdx index 499775c7..22116078 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -179,6 +179,18 @@ necessary for this build to succeed and can be found further down the page. @include 'builder/vmware/common/ExportConfig-not-required.mdx' +HCL Example: + +```hcl +source "vmware-iso" "example" { + # ... omitted for brevity ... + format = "ova" + ovftool_options = ["--compress=9", "--makeDeltaDisks"] + skip_export = false + keep_registered = false + skip_compaction = true +``` + ### Communicator configuration #### Optional common fields: diff --git a/docs/builders/vmx.mdx b/docs/builders/vmx.mdx index 20493898..0dba8719 100644 --- a/docs/builders/vmx.mdx +++ b/docs/builders/vmx.mdx @@ -130,6 +130,16 @@ necessary for this build to succeed and can be found further down the page. @include 'builder/vmware/common/ExportConfig-not-required.mdx' +HCL Example: + +```hcl + format = "ova" + ovftool_options = ["--compress=9", "--makeDeltaDisks"] + skip_export = false + keep_registered = false + skip_compaction = true +``` + ### Output configuration #### Optional: