Repository: vmware/govmomi
Pull request #3458 (2024-05-31 16:21:54)

api: add sata controller

## Description

Adds support for creating SATA controllers.

Closes: #1219

## Type of change

Please mark options that are relevant:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to
  not work as expected)
- [ ] This change requires a documentation update
- [ ] Build related change

## How Has This Been Tested?

- [x] Local test: `TestCreateSATAController`

```sh
Running tool: /usr/local/bin/go test -timeout 30s -run ^TestCreateSATAController$ github.com/vmware/govmomi/object

ok  	github.com/vmware/govmomi/object	(cached)
```

## Checklist:

- [x] My code follows the `CONTRIBUTION` [guidelines] of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged

[guidelines]: https://github.com/vmware/govmomi/blob/main/CONTRIBUTING.md


tenthirtyam: I think that I've addressed your concerns, but let me know otherwise.

I still want to do a round of additional tests before merge.

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index d2b3baba..2a47d9d0 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,41 +1,71 @@
-name: CodeQL
-"on":
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
   push:
-    branches:
-      - main
+    branches: [ main ]
   pull_request:
-    branches:
-      - main
+    # The branches below must be a subset of the branches above
+    branches: [ main ]
   schedule:
-    - cron: 30 23 * * 06
+    - cron: '30 23 * * 6'
+
 jobs:
   analyze:
     name: Analyze
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     permissions:
       actions: read
       contents: read
       security-events: write
+
     strategy:
       fail-fast: false
       matrix:
-        language:
-          - go
+        language: [ 'go' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+        # Learn more:
+        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
     steps:
-      - name: Checkout repository
-        uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
-      # Workaround for Go 1.21 compatibility.
-      # TODO: Remove when GitHub Action runners Support Go 1.21+.
-      - name: Setup Go
-        uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
-        with:
-          go-version-file: "go.mod"
-          cache: false
-      - name: Initialize CodeQL
-        uses: github/codeql-action/init@c4fb451437765abf5018c6fbf22cce1a7da1e5cc # codeql-bundle-v2.17.1
-        with:
-          languages: "${{ matrix.language }}"
-      - name: Autobuild
-        uses: github/codeql-action/autobuild@c4fb451437765abf5018c6fbf22cce1a7da1e5cc # codeql-bundle-v2.17.1
-      - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@c4fb451437765abf5018c6fbf22cce1a7da1e5cc # codeql-bundle-v2.17.1
+    - name: Checkout repository
+      uses: actions/checkout@v4
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v3
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v3
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/govmomi-release.yaml b/.github/workflows/govmomi-release.yaml
index ff5b79d9..9e191dd0 100644
--- a/.github/workflows/govmomi-release.yaml
+++ b/.github/workflows/govmomi-release.yaml
@@ -72,7 +72,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v5
         with:
-          go-version: '1.22'
+          go-version: '1.20'
 
       - name: Update version.go
         run: |
diff --git a/go.mod b/go.mod
index dd799c5f..883b64d0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/vmware/govmomi
 
-go 1.21
+go 1.19
 
 require (
 	github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d
@@ -11,7 +11,6 @@ require (
 	github.com/stretchr/testify v1.9.0
 	github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728
 	github.com/xlab/treeprint v1.2.0
-	golang.org/x/text v0.16.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index b6586412..17c83ddd 100644
--- a/go.sum
+++ b/go.sum
@@ -31,8 +31,6 @@ github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728 h1:sH9mEk+fly
 github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
 github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
 github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/govc/USAGE.md b/govc/USAGE.md
index ac6c0eaa..87b6d30a 100644
--- a/govc/USAGE.md
+++ b/govc/USAGE.md
@@ -6869,10 +6869,6 @@ Options:
   -ds=                       Datastore [GOVC_DATASTORE]
   -folder=                   Inventory folder [GOVC_FOLDER]
   -host=                     Host system [GOVC_HOST]
-  -net=                      Network [GOVC_NETWORK]
-  -net.adapter=e1000         Network adapter type
-  -net.address=              Network hardware address
-  -net.protocol=             Network device protocol. Applicable to vmxnet3vrdma. Default to 'rocev2'
   -pool=                     Resource pool [GOVC_RESOURCE_POOL]
   -priority=defaultPriority  The task priority
   -vm=                       Virtual machine [GOVC_VM]
diff --git a/govc/flags/output.go b/govc/flags/output.go
index 8975285e..c0338b18 100644
--- a/govc/flags/output.go
+++ b/govc/flags/output.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -26,11 +26,11 @@ import (
 	"os"
 	"reflect"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/dougm/pretty"
 
-	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/task"
 	"github.com/vmware/govmomi/vim25/progress"
 	"github.com/vmware/govmomi/vim25/soap"
@@ -50,7 +50,6 @@ type OutputFlag struct {
 	TTY  bool
 	Dump bool
 	Out  io.Writer
-	Spec bool
 
 	formatError  bool
 	formatIndent bool
@@ -73,9 +72,6 @@ func (flag *OutputFlag) Register(ctx context.Context, f *flag.FlagSet) {
 		f.BoolVar(&flag.JSON, "json", false, "Enable JSON output")
 		f.BoolVar(&flag.XML, "xml", false, "Enable XML output")
 		f.BoolVar(&flag.Dump, "dump", false, "Enable Go output")
-		if cli.ShowUnreleased() {
-			f.BoolVar(&flag.Spec, "spec", false, "Output spec without sending request")
-		}
 		// Avoid adding more flags for now..
 		flag.formatIndent = os.Getenv("GOVC_INDENT") != "false"      // Default to indented output
 		flag.formatError = os.Getenv("GOVC_FORMAT_ERROR") != "false" // Default to formatted errors
@@ -163,25 +159,6 @@ func dumpValue(val interface{}) interface{} {
 	return val
 }
 
-type outputAny struct {
-	Value any
-}
-
-func (*outputAny) Write(io.Writer) error {
-	return nil
-}
-
-func (a *outputAny) Dump() interface{} {
-	return a.Value
-}
-
-func (flag *OutputFlag) WriteAny(val any) error {
-	if !flag.All() {
-		flag.XML = true
-	}
-	return flag.WriteResult(&outputAny{val})
-}
-
 func (flag *OutputFlag) WriteResult(result OutputWriter) error {
 	var err error
 
@@ -227,7 +204,7 @@ type errorOutput struct {
 }
 
 func (e errorOutput) Write(w io.Writer) error {
-	reason := e.Error()
+	reason := e.error.Error()
 	var messages []string
 	var faults []types.LocalizableMessage
 
@@ -284,15 +261,15 @@ func (e errorOutput) canEncode() bool {
 	return soap.IsSoapFault(e.error) || soap.IsVimFault(e.error)
 }
 
-// errCannotEncode causes cli.Run to output err.Error() as it would without an error format specified
-var errCannotEncode = errors.New("cannot encode error")
+// cannotEncode causes cli.Run to output err.Error() as it would without an error format specified
+var cannotEncode = errors.New("cannot encode error")
 
 func (e errorOutput) MarshalJSON() ([]byte, error) {
 	_, ok := e.error.(json.Marshaler)
 	if ok || e.canEncode() {
 		return json.Marshal(e.error)
 	}
-	return nil, errCannotEncode
+	return nil, cannotEncode
 }
 
 func (e errorOutput) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error {
@@ -300,9 +277,108 @@ func (e errorOutput) MarshalXML(encoder *xml.Encoder, start xml.StartElement) er
 	if ok || e.canEncode() {
 		return encoder.Encode(e.error)
 	}
-	return errCannotEncode
+	return cannotEncode
+}
+
+type progressLogger struct {
+	flag   *OutputFlag
+	prefix string
+
+	wg sync.WaitGroup
+
+	sink chan chan progress.Report
+	done chan struct{}
+}
+
+func newProgressLogger(flag *OutputFlag, prefix string) *progressLogger {
+	p := &progressLogger{
+		flag:   flag,
+		prefix: prefix,
+
+		sink: make(chan chan progress.Report),
+		done: make(chan struct{}),
+	}
+
+	p.wg.Add(1)
+
+	go p.loopA()
+
+	return p
+}
+
+// loopA runs before Sink() has been called.
+func (p *progressLogger) loopA() {
+	var err error
+
+	defer p.wg.Done()
+
+	tick := time.NewTicker(100 * time.Millisecond)
+	defer tick.Stop()
+
+	called := false
+
+	for stop := false; !stop; {
+		select {
+		case ch := <-p.sink:
+			err = p.loopB(tick, ch)
+			stop = true
+			called = true
+		case <-p.done:
+			stop = true
+		case <-tick.C:
+			line := fmt.Sprintf("\r%s", p.prefix)
+			p.flag.Log(line)
+		}
+	}
+
+	if err != nil && err != io.EOF {
+		p.flag.Log(fmt.Sprintf("\r%sError: %s\n", p.prefix, err))
+	} else if called {
+		p.flag.Log(fmt.Sprintf("\r%sOK\n", p.prefix))
+	}
+}
+
+// loopA runs after Sink() has been called.
+func (p *progressLogger) loopB(tick *time.Ticker, ch <-chan progress.Report) error {
+	var r progress.Report
+	var ok bool
+	var err error
+
+	for ok = true; ok; {
+		select {
+		case r, ok = <-ch:
+			if !ok {
+				break
+			}
+			err = r.Error()
+		case <-tick.C:
+			line := fmt.Sprintf("\r%s", p.prefix)
+			if r != nil {
+				line += fmt.Sprintf("(%.0f%%", r.Percentage())
+				detail := r.Detail()
+				if detail != "" {
+					line += fmt.Sprintf(", %s", detail)
+				}
+				line += ")"
+			}
+			p.flag.Log(line)
+		}
+	}
+
+	return err
+}
+
+func (p *progressLogger) Sink() chan<- progress.Report {
+	ch := make(chan progress.Report)
+	p.sink <- ch
+	return ch
+}
+
+func (p *progressLogger) Wait() {
+	close(p.done)
+	p.wg.Wait()
 }
 
-func (flag *OutputFlag) ProgressLogger(prefix string) *progress.ProgressLogger {
-	return progress.NewProgressLogger(flag.Log, prefix)
+func (flag *OutputFlag) ProgressLogger(prefix string) *progressLogger {
+	return newProgressLogger(flag, prefix)
 }
diff --git a/ovf/importer/archive.go b/govc/importx/archive.go
similarity index 69%
rename from ovf/importer/archive.go
rename to govc/importx/archive.go
index 6b3f631f..fef36abd 100644
--- a/ovf/importer/archive.go
+++ b/govc/importx/archive.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package importer
+package importx
 
 import (
 	"archive/tar"
 	"bytes"
 	"context"
 	"errors"
+	"flag"
 	"fmt"
 	"io"
 	"net/url"
@@ -30,12 +31,32 @@ import (
 	"strings"
 
 	"github.com/vmware/govmomi/ovf"
+	"github.com/vmware/govmomi/vapi/library"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/soap"
 )
 
-func ReadOvf(fpath string, a Archive) ([]byte, error) {
-	r, _, err := a.Open(fpath)
+// ArchiveFlag doesn't register any flags;
+// only encapsulates some common archive related functionality.
+type ArchiveFlag struct {
+	Archive
+
+	manifest map[string]*library.Checksum
+}
+
+func newArchiveFlag(ctx context.Context) (*ArchiveFlag, context.Context) {
+	return &ArchiveFlag{}, ctx
+}
+
+func (f *ArchiveFlag) Register(ctx context.Context, fs *flag.FlagSet) {
+}
+
+func (f *ArchiveFlag) Process(ctx context.Context) error {
+	return nil
+}
+
+func (f *ArchiveFlag) ReadOvf(fpath string) ([]byte, error) {
+	r, _, err := f.Open(fpath)
 	if err != nil {
 		return nil, err
 	}
@@ -44,7 +65,7 @@ func ReadOvf(fpath string, a Archive) ([]byte, error) {
 	return io.ReadAll(r)
 }
 
-func ReadEnvelope(data []byte) (*ovf.Envelope, error) {
+func (f *ArchiveFlag) ReadEnvelope(data []byte) (*ovf.Envelope, error) {
 	e, err := ovf.Unmarshal(bytes.NewReader(data))
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse ovf: %s", err)
@@ -53,6 +74,22 @@ func ReadEnvelope(data []byte) (*ovf.Envelope, error) {
 	return e, nil
 }
 
+func (f *ArchiveFlag) readManifest(fpath string) error {
+	base := filepath.Base(fpath)
+	ext := filepath.Ext(base)
+	mfName := strings.Replace(base, ext, ".mf", 1)
+
+	mf, _, err := f.Open(mfName)
+	if err != nil {
+		msg := fmt.Sprintf("manifest %q: %s", mf, err)
+		fmt.Fprintln(os.Stderr, msg)
+		return errors.New(msg)
+	}
+	f.manifest, err = library.ReadManifest(mf)
+	_ = mf.Close()
+	return err
+}
+
 type Archive interface {
 	Open(string) (io.ReadCloser, int64, error)
 }
@@ -126,7 +163,7 @@ type Opener struct {
 	*vim25.Client
 }
 
-func IsRemotePath(path string) bool {
+func isRemotePath(path string) bool {
 	if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
 		return true
 	}
@@ -148,7 +185,7 @@ func (o Opener) OpenLocal(path string) (io.ReadCloser, int64, error) {
 }
 
 func (o Opener) OpenFile(path string) (io.ReadCloser, int64, error) {
-	if IsRemotePath(path) {
+	if isRemotePath(path) {
 		return o.OpenRemote(path)
 	}
 	return o.OpenLocal(path)
diff --git a/ovf/importer/importable.go b/govc/importx/importable.go
similarity index 91%
rename from ovf/importer/importable.go
rename to govc/importx/importable.go
index a43c7fc5..14e31670 100644
--- a/ovf/importer/importable.go
+++ b/govc/importx/importable.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package importer
+package importx
 
 import (
 	"fmt"
diff --git a/govc/importx/options.go b/govc/importx/options.go
index ddea6e38..3d8190ec 100644
--- a/govc/importx/options.go
+++ b/govc/importx/options.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2015-2023 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -26,12 +26,78 @@ import (
 	"github.com/vmware/govmomi/govc/flags"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/ovf"
-	"github.com/vmware/govmomi/ovf/importer"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
+type KeyValue struct {
+	Key   string
+	Value string
+}
+
+// case insensitive for Key + Value
+func (kv *KeyValue) UnmarshalJSON(b []byte) error {
+	e := struct {
+		types.KeyValue
+		Key   *string
+		Value *string
+	}{
+		types.KeyValue{}, &kv.Key, &kv.Value,
+	}
+
+	err := json.Unmarshal(b, &e)
+	if err != nil {
+		return err
+	}
+
+	if kv.Key == "" {
+		kv.Key = e.KeyValue.Key // "key"
+	}
+
+	if kv.Value == "" {
+		kv.Value = e.KeyValue.Value // "value"
+	}
+
+	return nil
+}
+
+type Property struct {
+	KeyValue
+	Spec *ovf.Property `json:",omitempty"`
+}
+
+type Network struct {
+	Name    string
+	Network string
+}
+
+type Options struct {
+	AllDeploymentOptions []string `json:",omitempty"`
+	Deployment           string   `json:",omitempty"`
+
+	AllDiskProvisioningOptions []string `json:",omitempty"`
+	DiskProvisioning           string
+
+	AllIPAllocationPolicyOptions []string `json:",omitempty"`
+	IPAllocationPolicy           string
+
+	AllIPProtocolOptions []string `json:",omitempty"`
+	IPProtocol           string
+
+	PropertyMapping []Property `json:",omitempty"`
+
+	NetworkMapping []Network `json:",omitempty"`
+
+	Annotation string `json:",omitempty"`
+
+	MarkAsTemplate bool
+	PowerOn        bool
+	InjectOvfEnv   bool
+	WaitForIP      bool
+	Name           *string
+}
+
 type OptionsFlag struct {
-	Options importer.Options
+	Options Options
 
 	path string
 }
diff --git a/govc/importx/ova.go b/govc/importx/ova.go
index 67712050..343cdcdd 100644
--- a/govc/importx/ova.go
+++ b/govc/importx/ova.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,7 +22,6 @@ import (
 
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/ovf/importer"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -44,21 +43,21 @@ func (cmd *ova) Run(ctx context.Context, f *flag.FlagSet) error {
 		return err
 	}
 
-	archive := &importer.TapeArchive{Path: fpath}
-	archive.Client = cmd.Importer.Client
+	archive := &TapeArchive{Path: fpath}
+	archive.Client = cmd.Client
 
-	cmd.Importer.Archive = archive
+	cmd.Archive = archive
 
 	moref, err := cmd.Import(fpath)
 	if err != nil {
 		return err
 	}
 
-	vm := object.NewVirtualMachine(cmd.Importer.Client, *moref)
+	vm := object.NewVirtualMachine(cmd.Client, *moref)
 	return cmd.Deploy(vm, cmd.OutputFlag)
 }
 
 func (cmd *ova) Import(fpath string) (*types.ManagedObjectReference, error) {
 	ovf := "*.ovf"
-	return cmd.Importer.Import(context.TODO(), ovf, cmd.Options)
+	return cmd.ovfx.Import(ovf)
 }
diff --git a/govc/importx/ovf.go b/govc/importx/ovf.go
index c903fc9a..e1765f4c 100644
--- a/govc/importx/ovf.go
+++ b/govc/importx/ovf.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -17,14 +17,23 @@ limitations under the License.
 package importx
 
 import (
+	"bytes"
 	"context"
 	"errors"
 	"flag"
+	"fmt"
+	"path"
+	"strings"
 
+	"github.com/vmware/govmomi/find"
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/nfc"
 	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/ovf/importer"
+	"github.com/vmware/govmomi/ovf"
+	"github.com/vmware/govmomi/vim25"
+	"github.com/vmware/govmomi/vim25/soap"
+	"github.com/vmware/govmomi/vim25/types"
 )
 
 type ovfx struct {
@@ -34,9 +43,17 @@ type ovfx struct {
 	*flags.ResourcePoolFlag
 	*flags.FolderFlag
 
+	*ArchiveFlag
 	*OptionsFlag
 
-	Importer importer.Importer
+	Name           string
+	VerifyManifest bool
+	Hidden         bool
+
+	Client       *vim25.Client
+	Datacenter   *object.Datacenter
+	Datastore    *object.Datastore
+	ResourcePool *object.ResourcePool
 }
 
 func init() {
@@ -55,12 +72,14 @@ func (cmd *ovfx) Register(ctx context.Context, f *flag.FlagSet) {
 	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
 	cmd.FolderFlag.Register(ctx, f)
 
+	cmd.ArchiveFlag, ctx = newArchiveFlag(ctx)
+	cmd.ArchiveFlag.Register(ctx, f)
 	cmd.OptionsFlag, ctx = newOptionsFlag(ctx)
 	cmd.OptionsFlag.Register(ctx, f)
 
-	f.StringVar(&cmd.Importer.Name, "name", "", "Name to use for new entity")
-	f.BoolVar(&cmd.Importer.VerifyManifest, "m", false, "Verify checksum of uploaded files against manifest (.mf)")
-	f.BoolVar(&cmd.Importer.Hidden, "hidden", false, "Enable hidden properties")
+	f.StringVar(&cmd.Name, "name", "", "Name to use for new entity")
+	f.BoolVar(&cmd.VerifyManifest, "m", false, "Verify checksum of uploaded files against manifest (.mf)")
+	f.BoolVar(&cmd.Hidden, "hidden", false, "Enable hidden properties")
 }
 
 func (cmd *ovfx) Process(ctx context.Context) error {
@@ -76,6 +95,9 @@ func (cmd *ovfx) Process(ctx context.Context) error {
 	if err := cmd.ResourcePoolFlag.Process(ctx); err != nil {
 		return err
 	}
+	if err := cmd.ArchiveFlag.Process(ctx); err != nil {
+		return err
+	}
 	if err := cmd.OptionsFlag.Process(ctx); err != nil {
 		return err
 	}
@@ -95,17 +117,17 @@ func (cmd *ovfx) Run(ctx context.Context, f *flag.FlagSet) error {
 		return err
 	}
 
-	archive := &importer.FileArchive{Path: fpath}
-	archive.Client = cmd.Importer.Client
+	archive := &FileArchive{Path: fpath}
+	archive.Client = cmd.Client
 
-	cmd.Importer.Archive = archive
+	cmd.Archive = archive
 
-	moref, err := cmd.Importer.Import(context.TODO(), fpath, cmd.Options)
+	moref, err := cmd.Import(fpath)
 	if err != nil {
 		return err
 	}
 
-	vm := object.NewVirtualMachine(cmd.Importer.Client, *moref)
+	vm := object.NewVirtualMachine(cmd.Client, *moref)
 	return cmd.Deploy(vm, cmd.OutputFlag)
 }
 
@@ -117,70 +139,313 @@ func (cmd *ovfx) Prepare(f *flag.FlagSet) (string, error) {
 		return "", errors.New("no file specified")
 	}
 
-	cmd.Importer.Log = cmd.OutputFlag.Log
-	cmd.Importer.Client, err = cmd.DatastoreFlag.Client()
+	cmd.Client, err = cmd.DatastoreFlag.Client()
 	if err != nil {
 		return "", err
 	}
 
-	cmd.Importer.Datacenter, err = cmd.DatastoreFlag.Datacenter()
+	cmd.Datacenter, err = cmd.DatastoreFlag.Datacenter()
 	if err != nil {
 		return "", err
 	}
 
-	cmd.Importer.Datastore, err = cmd.DatastoreFlag.Datastore()
+	cmd.Datastore, err = cmd.DatastoreFlag.Datastore()
 	if err != nil {
 		return "", err
 	}
 
-	cmd.Importer.ResourcePool, err = cmd.ResourcePoolIfSpecified()
+	cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePoolIfSpecified()
 	if err != nil {
 		return "", err
 	}
 
+	return f.Arg(0), nil
+}
+
+func (cmd *ovfx) Map(op []Property) (p []types.KeyValue) {
+	for _, v := range op {
+		p = append(p, types.KeyValue{
+			Key:   v.Key,
+			Value: v.Value,
+		})
+	}
+
+	return
+}
+
+func (cmd *ovfx) validateNetwork(e *ovf.Envelope, net Network) {
+	var names []string
+
+	if e.Network != nil {
+		for _, n := range e.Network.Networks {
+			if n.Name == net.Name {
+				return
+			}
+			names = append(names, n.Name)
+		}
+	}
+
+	_, _ = cmd.Log(fmt.Sprintf("Warning: invalid NetworkMapping.Name=%q, valid names=%s\n", net.Name, names))
+}
+
+func (cmd *ovfx) NetworkMap(e *ovf.Envelope) ([]types.OvfNetworkMapping, error) {
+	ctx := context.TODO()
+	finder, err := cmd.DatastoreFlag.Finder()
+	if err != nil {
+		return nil, err
+	}
+
+	var nmap []types.OvfNetworkMapping
+	for _, m := range cmd.Options.NetworkMapping {
+		if m.Network == "" {
+			continue // Not set, let vSphere choose the default network
+		}
+		cmd.validateNetwork(e, m)
+
+		var ref types.ManagedObjectReference
+
+		net, err := finder.Network(ctx, m.Network)
+		if err != nil {
+			switch err.(type) {
+			case *find.NotFoundError:
+				if !ref.FromString(m.Network) {
+					return nil, err
+				} // else this is a raw MO ref
+			default:
+				return nil, err
+			}
+		} else {
+			ref = net.Reference()
+		}
+
+		nmap = append(nmap, types.OvfNetworkMapping{
+			Name:    m.Name,
+			Network: ref,
+		})
+	}
+
+	return nmap, err
+}
+
+func (cmd *ovfx) Import(fpath string) (*types.ManagedObjectReference, error) {
+	ctx := context.TODO()
+
+	o, err := cmd.ReadOvf(fpath)
+	if err != nil {
+		return nil, err
+	}
+
+	e, err := cmd.ReadEnvelope(o)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse ovf: %s", err)
+	}
+
+	name := "Govc Virtual Appliance"
+	if e.VirtualSystem != nil {
+		name = e.VirtualSystem.ID
+		if e.VirtualSystem.Name != nil {
+			name = *e.VirtualSystem.Name
+		}
+
+		if cmd.Hidden {
+			// TODO: userConfigurable is optional and defaults to false, so we should *add* userConfigurable=true
+			// if not set for a Property. But, there'd be a bunch more work involved to preserve other data in doing
+			// a complete xml.Marshal of the .ovf
+			o = bytes.ReplaceAll(o, []byte(`userConfigurable="false"`), []byte(`userConfigurable="true"`))
+		}
+	}
+
+	// Override name from options if specified
+	if cmd.Options.Name != nil {
+		name = *cmd.Options.Name
+	}
+
+	// Override name from arguments if specified
+	if cmd.Name != "" {
+		name = cmd.Name
+	}
+
+	nmap, err := cmd.NetworkMap(e)
+	if err != nil {
+		return nil, err
+	}
+
+	cisp := types.OvfCreateImportSpecParams{
+		DiskProvisioning:   cmd.Options.DiskProvisioning,
+		EntityName:         name,
+		IpAllocationPolicy: cmd.Options.IPAllocationPolicy,
+		IpProtocol:         cmd.Options.IPProtocol,
+		OvfManagerCommonParams: types.OvfManagerCommonParams{
+			DeploymentOption: cmd.Options.Deployment,
+			Locale:           "US"},
+		PropertyMapping: cmd.Map(cmd.Options.PropertyMapping),
+		NetworkMapping:  nmap,
+	}
+
 	host, err := cmd.HostSystemIfSpecified()
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 
-	if cmd.Importer.ResourcePool == nil {
+	if cmd.ResourcePool == nil {
 		if host == nil {
-			cmd.Importer.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool()
+			cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool()
 		} else {
-			cmd.Importer.ResourcePool, err = host.ResourcePool(context.TODO())
+			cmd.ResourcePool, err = host.ResourcePool(ctx)
 		}
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 	}
 
-	cmd.Importer.Finder, err = cmd.DatastoreFlag.Finder()
+	m := ovf.NewManager(cmd.Client)
+	spec, err := m.CreateImportSpec(ctx, string(o), cmd.ResourcePool, cmd.Datastore, cisp)
 	if err != nil {
-		return "", err
+		return nil, err
+	}
+	if spec.Error != nil {
+		return nil, errors.New(spec.Error[0].LocalizedMessage)
+	}
+	if spec.Warning != nil {
+		for _, w := range spec.Warning {
+			_, _ = cmd.Log(fmt.Sprintf("Warning: %s\n", w.LocalizedMessage))
+		}
 	}
 
-	cmd.Importer.Host, err = cmd.HostSystemIfSpecified()
-	if err != nil {
-		return "", err
+	if cmd.Options.Annotation != "" {
+		switch s := spec.ImportSpec.(type) {
+		case *types.VirtualMachineImportSpec:
+			s.ConfigSpec.Annotation = cmd.Options.Annotation
+		case *types.VirtualAppImportSpec:
+			s.VAppConfigSpec.Annotation = cmd.Options.Annotation
+		}
 	}
 
+	var folder *object.Folder
 	// The folder argument must not be set on a VM in a vApp, otherwise causes
 	// InvalidArgument fault: A specified parameter was not correct: pool
-	if cmd.Importer.ResourcePool.Reference().Type != "VirtualApp" {
-		cmd.Importer.Folder, err = cmd.FolderOrDefault("vm")
+	if cmd.ResourcePool.Reference().Type != "VirtualApp" {
+		folder, err = cmd.FolderOrDefault("vm")
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 	}
 
-	if cmd.Importer.Name == "" {
-		// Override name from options if specified
-		if cmd.Options.Name != nil {
-			cmd.Importer.Name = *cmd.Options.Name
+	if cmd.VerifyManifest {
+		err = cmd.readManifest(fpath)
+		if err != nil {
+			return nil, err
 		}
-	} else {
-		cmd.Options.Name = &cmd.Importer.Name
 	}
 
-	return f.Arg(0), nil
+	lease, err := cmd.ResourcePool.ImportVApp(ctx, spec.ImportSpec, folder, host)
+	if err != nil {
+		return nil, err
+	}
+
+	info, err := lease.Wait(ctx, spec.FileItem)
+	if err != nil {
+		return nil, err
+	}
+
+	u := lease.StartUpdater(ctx, info)
+	defer u.Done()
+
+	for _, i := range info.Items {
+		err = cmd.Upload(ctx, lease, i)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &info.Entity, lease.Complete(ctx)
+}
+
+func (cmd *ovfx) Upload(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error {
+	file := item.Path
+
+	f, size, err := cmd.Open(file)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	logger := cmd.ProgressLogger(fmt.Sprintf("Uploading %s... ", path.Base(file)))
+	defer logger.Wait()
+
+	opts := soap.Upload{
+		ContentLength: size,
+		Progress:      logger,
+	}
+
+	err = lease.Upload(ctx, item, f, opts)
+	if err != nil {
+		return err
+	}
+
+	if cmd.VerifyManifest {
+		mapImportKeyToKey := func(urls []types.HttpNfcLeaseDeviceUrl, importKey string) string {
+			for _, url := range urls {
+				if url.ImportKey == importKey {
+					return url.Key
+				}
+			}
+			return ""
+		}
+		leaseInfo, err := lease.Wait(ctx, nil)
+		if err != nil {
+			return err
+		}
+		return cmd.validateChecksum(ctx, lease, file, mapImportKeyToKey(leaseInfo.DeviceUrl, item.DeviceId))
+	}
+	return nil
+}
+
+func (cmd *ovfx) validateChecksum(ctx context.Context, lease *nfc.Lease, file string, key string) error {
+	sum, found := cmd.manifest[file]
+	if !found {
+		msg := fmt.Sprintf("missing checksum for %v in manifest file", file)
+		return errors.New(msg)
+	}
+	// Perform the checksum match eagerly, after each file upload, instead
+	// of after uploading all the files, to provide fail-fast behavior.
+	// (Trade-off here is multiple GetManifest() API calls to the server.)
+	manifests, err := lease.GetManifest(ctx)
+	if err != nil {
+		return err
+	}
+	for _, m := range manifests {
+		if m.Key == key {
+			// Compare server-side computed checksum of uploaded file
+			// against the client's manifest entry (assuming client's
+			// manifest has correct checksums - client doesn't compute
+			// checksum of the file before uploading).
+
+			// Try matching sha1 first (newer versions have moved to sha256).
+			if strings.ToUpper(sum.Algorithm) == "SHA1" {
+				if sum.Checksum != m.Sha1 {
+					msg := fmt.Sprintf("manifest checksum %v mismatch with uploaded checksum %v for file %v",
+						sum.Checksum, m.Sha1, file)
+					return errors.New(msg)
+				}
+				// Uploaded file checksum computed by server matches with local manifest entry.
+				return nil
+			}
+			// If not sha1, check for other types (in a separate field).
+			if !strings.EqualFold(sum.Algorithm, m.ChecksumType) {
+				msg := fmt.Sprintf("manifest checksum type %v mismatch with uploaded checksum type %v for file %v",
+					sum.Algorithm, m.ChecksumType, file)
+				return errors.New(msg)
+			}
+			if !strings.EqualFold(sum.Checksum, m.Checksum) {
+				msg := fmt.Sprintf("manifest checksum %v mismatch with uploaded checksum %v for file %v",
+					sum.Checksum, m.Checksum, file)
+				return errors.New(msg)
+			}
+			// Uploaded file checksum computed by server matches with local manifest entry.
+			return nil
+		}
+	}
+	msg := fmt.Sprintf("missing manifest entry on server for uploaded file %v (key %v), manifests=%#v", file, key, manifests)
+	return errors.New(msg)
 }
diff --git a/govc/importx/spec.go b/govc/importx/spec.go
index 01dcf0b6..acb1729f 100644
--- a/govc/importx/spec.go
+++ b/govc/importx/spec.go
@@ -22,18 +22,27 @@ import (
 	"fmt"
 	"io"
 	"path"
+	"strings"
 
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
-	"github.com/vmware/govmomi/ovf/importer"
+	"github.com/vmware/govmomi/ovf"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+var (
+	allDiskProvisioningOptions = types.OvfCreateImportSpecParamsDiskProvisioningType("").Strings()
+
+	allIPAllocationPolicyOptions = types.VAppIPAssignmentInfoIpAllocationPolicy("").Strings()
+
+	allIPProtocolOptions = types.VAppIPAssignmentInfoProtocols("").Strings()
 )
 
 type spec struct {
+	*ArchiveFlag
 	*flags.ClientFlag
 	*flags.OutputFlag
 
-	Archive importer.Archive
-
 	hidden bool
 }
 
@@ -42,6 +51,8 @@ func init() {
 }
 
 func (cmd *spec) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ArchiveFlag, ctx = newArchiveFlag(ctx)
+	cmd.ArchiveFlag.Register(ctx, f)
 	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
 	cmd.ClientFlag.Register(ctx, f)
 
@@ -52,6 +63,9 @@ func (cmd *spec) Register(ctx context.Context, f *flag.FlagSet) {
 }
 
 func (cmd *spec) Process(ctx context.Context) error {
+	if err := cmd.ArchiveFlag.Process(ctx); err != nil {
+		return err
+	}
 	if err := cmd.ClientFlag.Process(ctx); err != nil {
 		return err
 	}
@@ -72,29 +86,29 @@ func (cmd *spec) Run(ctx context.Context, f *flag.FlagSet) error {
 	if len(fpath) > 0 {
 		switch path.Ext(fpath) {
 		case ".ovf":
-			cmd.Archive = &importer.FileArchive{Path: fpath}
+			cmd.Archive = &FileArchive{Path: fpath}
 		case "", ".ova":
-			cmd.Archive = &importer.TapeArchive{Path: fpath}
+			cmd.Archive = &TapeArchive{Path: fpath}
 			fpath = "*.ovf"
 		default:
 			return fmt.Errorf("invalid file extension %s", path.Ext(fpath))
 		}
 
-		if importer.IsRemotePath(f.Arg(0)) {
+		if isRemotePath(f.Arg(0)) {
 			client, err := cmd.Client()
 			if err != nil {
 				return err
 			}
 			switch archive := cmd.Archive.(type) {
-			case *importer.FileArchive:
+			case *FileArchive:
 				archive.Client = client
-			case *importer.TapeArchive:
+			case *TapeArchive:
 				archive.Client = client
 			}
 		}
 	}
 
-	env, err := importer.Spec(fpath, cmd.Archive, cmd.hidden, cmd.Verbose())
+	env, err := cmd.Spec(fpath)
 	if err != nil {
 		return err
 	}
@@ -106,9 +120,114 @@ func (cmd *spec) Run(ctx context.Context, f *flag.FlagSet) error {
 }
 
 type specResult struct {
-	*importer.Options
+	*Options
 }
 
 func (*specResult) Write(w io.Writer) error {
 	return nil
 }
+
+func (cmd *spec) Map(e *ovf.Envelope) (res []Property) {
+	if e == nil || e.VirtualSystem == nil {
+		return nil
+	}
+
+	for _, p := range e.VirtualSystem.Product {
+		for i, v := range p.Property {
+			if v.UserConfigurable == nil {
+				continue
+			}
+			if !*v.UserConfigurable && !cmd.hidden {
+				continue
+			}
+
+			d := ""
+			if v.Default != nil {
+				d = *v.Default
+			}
+
+			// vSphere only accept True/False as boolean values for some reason
+			if v.Type == "boolean" {
+				d = strings.Title(d)
+			}
+
+			np := Property{KeyValue: KeyValue{Key: p.Key(v), Value: d}}
+
+			if cmd.Verbose() {
+				np.Spec = &p.Property[i]
+			}
+
+			res = append(res, np)
+		}
+	}
+
+	return
+}
+
+func (cmd *spec) Spec(fpath string) (*Options, error) {
+	e := &ovf.Envelope{}
+	if fpath != "" {
+		d, err := cmd.ReadOvf(fpath)
+		if err != nil {
+			return nil, err
+		}
+
+		if e, err = cmd.ReadEnvelope(d); err != nil {
+			return nil, err
+		}
+	}
+
+	var deploymentOptions []string
+	if e.DeploymentOption != nil && e.DeploymentOption.Configuration != nil {
+		// add default first
+		for _, c := range e.DeploymentOption.Configuration {
+			if c.Default != nil && *c.Default {
+				deploymentOptions = append(deploymentOptions, c.ID)
+			}
+		}
+
+		for _, c := range e.DeploymentOption.Configuration {
+			if c.Default == nil || !*c.Default {
+				deploymentOptions = append(deploymentOptions, c.ID)
+			}
+		}
+	}
+
+	o := Options{
+		DiskProvisioning:   allDiskProvisioningOptions[0],
+		IPAllocationPolicy: allIPAllocationPolicyOptions[0],
+		IPProtocol:         allIPProtocolOptions[0],
+		MarkAsTemplate:     false,
+		PowerOn:            false,
+		WaitForIP:          false,
+		InjectOvfEnv:       false,
+		PropertyMapping:    cmd.Map(e),
+	}
+
+	if deploymentOptions != nil {
+		o.Deployment = deploymentOptions[0]
+	}
+
+	if e.VirtualSystem != nil && e.VirtualSystem.Annotation != nil {
+		for _, a := range e.VirtualSystem.Annotation {
+			o.Annotation += a.Annotation
+		}
+	}
+
+	if e.Network != nil {
+		for _, net := range e.Network.Networks {
+			o.NetworkMapping = append(o.NetworkMapping, Network{net.Name, ""})
+		}
+	}
+
+	if cmd.Verbose() {
+		if deploymentOptions != nil {
+			o.AllDeploymentOptions = deploymentOptions
+		}
+		o.AllDiskProvisioningOptions = allDiskProvisioningOptions
+		o.AllIPAllocationPolicyOptions = allIPAllocationPolicyOptions
+		o.AllIPProtocolOptions = allIPProtocolOptions
+	}
+
+	return &o, nil
+}
diff --git a/govc/library/import.go b/govc/library/import.go
index a5f54e68..4b18d84f 100644
--- a/govc/library/import.go
+++ b/govc/library/import.go
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+	http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -29,7 +29,7 @@ import (
 
 	"github.com/vmware/govmomi/govc/cli"
 	"github.com/vmware/govmomi/govc/flags"
-	"github.com/vmware/govmomi/ovf/importer"
+	"github.com/vmware/govmomi/govc/importx"
 	"github.com/vmware/govmomi/vapi/library"
 	"github.com/vmware/govmomi/vim25/soap"
 )
@@ -112,9 +112,8 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error {
 	if err != nil {
 		return err
 	}
-	opener := importer.Opener{Client: client}
-	var archive importer.Archive
-	archive = &importer.FileArchive{Path: file, Opener: opener}
+	opener := importx.Opener{Client: client}
+	archive := &importx.ArchiveFlag{Archive: &importx.FileArchive{Path: file, Opener: opener}}
 
 	manifest := make(map[string]*library.Checksum)
 	if cmd.Name == "" {
@@ -123,7 +122,7 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error {
 
 	switch ext {
 	case ".ova":
-		archive = &importer.TapeArchive{Path: file, Opener: opener}
+		archive.Archive = &importx.TapeArchive{Path: file, Opener: opener}
 		base = "*.ovf"
 		mf = "*.mf"
 		kind = library.ItemTypeOVF
@@ -207,7 +206,7 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error {
 		}
 		defer f.Close()
 
-		if e, ok := f.(*importer.TapeArchiveEntry); ok {
+		if e, ok := f.(*importx.TapeArchiveEntry); ok {
 			name = e.Name // expand path.Match's (e.g. "*.ovf" -> "name.ovf")
 		}
 
@@ -229,7 +228,7 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error {
 		if err != nil {
 			return err
 		}
-		if cmd.TTY {
+		if cmd.OutputFlag.TTY {
 			logger := cmd.ProgressLogger(fmt.Sprintf("Uploading %s... ", name))
 			p.Progress = logger
 			defer logger.Wait()
@@ -242,12 +241,12 @@ func (cmd *item) Run(ctx context.Context, f *flag.FlagSet) error {
 	}
 
 	if cmd.Type == library.ItemTypeOVF {
-		o, err := importer.ReadOvf(base, archive)
+		o, err := archive.ReadOvf(base)
 		if err != nil {
 			return err
 		}
 
-		e, err := importer.ReadEnvelope(o)
+		e, err := archive.ReadEnvelope(o)
 		if err != nil {
 			return fmt.Errorf("failed to parse ovf: %s", err)
 		}
diff --git a/govc/main.go b/govc/main.go
index bae197e7..4943d3c5 100644
--- a/govc/main.go
+++ b/govc/main.go
@@ -113,7 +113,6 @@ import (
 	_ "github.com/vmware/govmomi/govc/vlcm/depot/content/baseimages"
 	_ "github.com/vmware/govmomi/govc/vlcm/depot/offline"
 	_ "github.com/vmware/govmomi/govc/vm"
-	_ "github.com/vmware/govmomi/govc/vm/check"
 	_ "github.com/vmware/govmomi/govc/vm/dataset"
 	_ "github.com/vmware/govmomi/govc/vm/dataset/entry"
 	_ "github.com/vmware/govmomi/govc/vm/disk"
diff --git a/govc/vm/check/compat.go b/govc/vm/check/compat.go
deleted file mode 100644
index 3702236f..00000000
--- a/govc/vm/check/compat.go
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package check
-
-import (
-	"context"
-	"flag"
-
-	"github.com/vmware/govmomi/govc/cli"
-)
-
-type compat struct {
-	checkFlag
-}
-
-func init() {
-	cli.Register("vm.check.compat", &compat{}, true)
-}
-
-func (cmd *compat) Description() string {
-	return `Check if VM can be placed on the given HOST in the given resource POOL.
-
-Examples:
-  govc vm.check.compat -vm my-vm -pool $pool`
-}
-
-func (cmd *compat) Run(ctx context.Context, f *flag.FlagSet) error {
-	vm, err := cmd.VirtualMachine()
-	if err != nil {
-		return err
-	}
-
-	if vm == nil {
-		return flag.ErrHelp
-	}
-
-	checker, err := cmd.compatChecker()
-	if err != nil {
-		return err
-	}
-
-	res, err := checker.CheckCompatibility(ctx, vm.Reference(), cmd.Host, cmd.Pool, cmd.Test...)
-	if err != nil {
-		return err
-	}
-
-	return cmd.result(ctx, res)
-}
diff --git a/govc/vm/check/config.go b/govc/vm/check/config.go
deleted file mode 100644
index 2c53a5e5..00000000
--- a/govc/vm/check/config.go
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package check
-
-import (
-	"context"
-	"flag"
-
-	"github.com/vmware/govmomi/govc/cli"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type config struct {
-	checkFlag
-}
-
-func init() {
-	cli.Register("vm.check.config", &config{}, true)
-}
-
-func (cmd *config) Description() string {
-	return `Check if VM config spec can be applied.
-
-Examples:
-  govc vm.create -spec ... | govc vm.check.config -pool $pool`
-}
-
-func (cmd *config) Run(ctx context.Context, f *flag.FlagSet) error {
-	var spec types.VirtualMachineConfigSpec
-
-	if err := cmd.Spec(&spec); err != nil {
-		return err
-	}
-
-	checker, err := cmd.compatChecker()
-	if err != nil {
-		return err
-	}
-
-	res, err := checker.CheckVmConfig(ctx, spec, cmd.Machine, cmd.Host, cmd.Pool, cmd.Test...)
-	if err != nil {
-		return err
-	}
-
-	return cmd.result(ctx, res)
-}
diff --git a/govc/vm/check/flag.go b/govc/vm/check/flag.go
deleted file mode 100644
index 079c3b07..00000000
--- a/govc/vm/check/flag.go
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package check
-
-import (
-	"context"
-	"flag"
-	"fmt"
-	"io"
-	"os"
-	"slices"
-	"strings"
-	"text/tabwriter"
-
-	"github.com/vmware/govmomi/find"
-	"github.com/vmware/govmomi/govc/flags"
-	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/vim25/types"
-	"github.com/vmware/govmomi/vim25/xml"
-)
-
-type checkFlag struct {
-	*flags.VirtualMachineFlag
-	*flags.HostSystemFlag
-	*flags.ResourcePoolFlag
-
-	Machine, Host, Pool *types.ManagedObjectReference
-
-	Test []string
-}
-
-func (cmd *checkFlag) Register(ctx context.Context, f *flag.FlagSet) {
-	cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
-	cmd.VirtualMachineFlag.Register(ctx, f)
-	cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx)
-	cmd.HostSystemFlag.Register(ctx, f)
-	cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx)
-	cmd.ResourcePoolFlag.Register(ctx, f)
-}
-
-func (cmd *checkFlag) Process(ctx context.Context) error {
-	if err := cmd.VirtualMachineFlag.Process(ctx); err != nil {
-		return err
-	}
-	if err := cmd.HostSystemFlag.Process(ctx); err != nil {
-		return err
-	}
-	if err := cmd.ResourcePoolFlag.Process(ctx); err != nil {
-		return err
-	}
-
-	vm, err := cmd.VirtualMachine()
-	if err != nil {
-		return err
-	}
-	if vm != nil {
-		cmd.Machine = types.NewReference(vm.Reference())
-	}
-
-	host, err := cmd.HostSystemIfSpecified()
-	if err != nil {
-		return err
-	}
-	if host != nil {
-		cmd.Host = types.NewReference(host.Reference())
-	}
-
-	pool, err := cmd.ResourcePoolIfSpecified()
-	if err != nil {
-		return err
-	}
-	if pool != nil {
-		cmd.Pool = types.NewReference(pool.Reference())
-	}
-
-	return nil
-}
-
-func (cmd *checkFlag) provChecker() (*object.VmProvisioningChecker, error) {
-	c, err := cmd.VirtualMachineFlag.Client()
-	if err != nil {
-		return nil, err
-	}
-
-	return object.NewVmProvisioningChecker(c), nil
-}
-
-func (cmd *checkFlag) compatChecker() (*object.VmCompatibilityChecker, error) {
-	c, err := cmd.VirtualMachineFlag.Client()
-	if err != nil {
-		return nil, err
-	}
-
-	return object.NewVmCompatibilityChecker(c), nil
-}
-
-func (cmd *checkFlag) Spec(spec any) error {
-	dec := xml.NewDecoder(os.Stdin)
-	dec.TypeFunc = types.TypeFunc()
-	return dec.Decode(spec)
-}
-
-// return cmd.VirtualMachineFlag.WriteResult(&checkResult{res, ctx, cmd.VirtualMachineFlag})
-func (cmd *checkFlag) result(ctx context.Context, res []types.CheckResult) error {
-	return cmd.VirtualMachineFlag.WriteResult(&checkResult{res, ctx, cmd.VirtualMachineFlag})
-}
-
-type checkResult struct {
-	Result []types.CheckResult `json:"result"`
-	ctx    context.Context
-	vm     *flags.VirtualMachineFlag
-}
-
-func (res *checkResult) Write(w io.Writer) error {
-	tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
-
-	c, err := res.vm.Client()
-	if err != nil {
-		return err
-	}
-
-	for _, r := range res.Result {
-		fields := []struct {
-			name  string
-			moid  *types.ManagedObjectReference
-			fault []types.LocalizedMethodFault
-		}{
-			{"VM", r.Vm, nil},
-			{"Host", r.Host, nil},
-			{"Warning", nil, r.Warning},
-			{"Error", nil, r.Error},
-		}
-
-		for _, f := range fields {
-			var val string
-			if f.moid == nil {
-				var msgs []string
-				for _, m := range f.fault {
-					msgs = append(msgs, m.LocalizedMessage)
-				}
-				val = strings.Join(slices.Compact(msgs), "\n\t")
-			} else {
-				val, err = find.InventoryPath(res.ctx, c, *f.moid)
-				if err != nil {
-					return err
-				}
-			}
-			fmt.Fprintf(tw, "%s:\t%s\n", f.name, val)
-		}
-	}
-
-	return tw.Flush()
-}
diff --git a/govc/vm/check/relocate.go b/govc/vm/check/relocate.go
deleted file mode 100644
index a33cfa5b..00000000
--- a/govc/vm/check/relocate.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package check
-
-import (
-	"context"
-	"flag"
-
-	"github.com/vmware/govmomi/govc/cli"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type relocate struct {
-	checkFlag
-}
-
-func init() {
-	cli.Register("vm.check.relocate", &relocate{}, true)
-}
-
-func (cmd *relocate) Description() string {
-	return `Check if VM can be relocated.
-
-Examples:
-  govc vm.migrate -spec my-vm | govc vm.check.relocate -vm my-vm`
-}
-
-func (cmd *relocate) Run(ctx context.Context, f *flag.FlagSet) error {
-	vm, err := cmd.VirtualMachine()
-	if err != nil {
-		return err
-	}
-
-	if vm == nil {
-		return flag.ErrHelp
-	}
-
-	var spec types.VirtualMachineRelocateSpec
-	if err := cmd.Spec(&spec); err != nil {
-		return err
-	}
-
-	checker, err := cmd.provChecker()
-	if err != nil {
-		return err
-	}
-
-	res, err := checker.CheckRelocate(ctx, vm.Reference(), spec, cmd.Test...)
-	if err != nil {
-		return err
-	}
-
-	return cmd.result(ctx, res)
-}
diff --git a/govc/vm/clone.go b/govc/vm/clone.go
index 0e85970e..d9ef49a2 100644
--- a/govc/vm/clone.go
+++ b/govc/vm/clone.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2016-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+	http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -243,9 +243,6 @@ func (cmd *clone) Run(ctx context.Context, f *flag.FlagSet) error {
 	if err != nil {
 		return err
 	}
-	if cmd.Spec {
-		return nil
-	}
 
 	if cmd.cpus > 0 || cmd.memory > 0 || cmd.annotation != "" {
 		vmConfigSpec := types.VirtualMachineConfigSpec{}
@@ -474,10 +471,6 @@ func (cmd *clone) cloneVM(ctx context.Context) (*object.VirtualMachine, error) {
 		cloneSpec.Customization = &customSpec
 	}
 
-	if cmd.Spec {
-		return nil, cmd.WriteAny(cloneSpec)
-	}
-
 	task, err := cmd.VirtualMachine.Clone(ctx, cmd.Folder, cmd.name, *cloneSpec)
 	if err != nil {
 		return nil, err
diff --git a/govc/vm/create.go b/govc/vm/create.go
index 84950dad..9a90ddf0 100644
--- a/govc/vm/create.go
+++ b/govc/vm/create.go
@@ -307,7 +307,7 @@ func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
 	if err != nil {
 		return err
 	}
-	if cmd.place || cmd.Spec {
+	if cmd.place {
 		return nil
 	}
 	info, err := task.WaitForResult(ctx, nil)
@@ -490,7 +490,7 @@ func (cmd *create) createVM(ctx context.Context) (*object.Task, error) {
 		return nil, fmt.Errorf("please provide either a cluster, datastore or datastore-cluster")
 	}
 
-	if !cmd.force && !cmd.Spec {
+	if !cmd.force {
 		vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name)
 
 		_, err := datastore.Stat(ctx, vmxPath)
@@ -506,10 +506,6 @@ func (cmd *create) createVM(ctx context.Context) (*object.Task, error) {
 		VmPathName: fmt.Sprintf("[%s]", datastore.Name()),
 	}
 
-	if cmd.Spec {
-		return nil, cmd.WriteAny(spec)
-	}
-
 	return folder.CreateVM(ctx, *spec, cmd.ResourcePool, cmd.HostSystem)
 }
 
@@ -523,6 +519,14 @@ func (cmd *create) addStorage(devices object.VirtualDeviceList) (object.VirtualD
 
 			devices = append(devices, nvme)
 			cmd.controller = devices.Name(nvme)
+		} else if cmd.controller == "sata" {
+			sata, err := devices.CreateSATAController()
+			if err != nil {
+				return nil, err
+			}
+
+			devices = append(devices, sata)
+			cmd.controller = devices.Name(sata)
 		} else {
 			scsi, err := devices.CreateSCSIController(cmd.controller)
 			if err != nil {
diff --git a/govc/vm/migrate.go b/govc/vm/migrate.go
index 0e6d1a9f..cf133e86 100644
--- a/govc/vm/migrate.go
+++ b/govc/vm/migrate.go
@@ -1,11 +1,11 @@
 /*
-Copyright (c) 2016-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2016 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
-http://www.apache.org/licenses/LICENSE-2.0
+    http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -32,7 +32,6 @@ type migrate struct {
 	*flags.ResourcePoolFlag
 	*flags.HostSystemFlag
 	*flags.DatastoreFlag
-	*flags.NetworkFlag
 	*flags.VirtualMachineFlag
 
 	priority types.VirtualMachineMovePriority
@@ -59,9 +58,6 @@ func (cmd *migrate) Register(ctx context.Context, f *flag.FlagSet) {
 	cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx)
 	cmd.DatastoreFlag.Register(ctx, f)
 
-	cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx)
-	cmd.NetworkFlag.Register(ctx, f)
-
 	f.StringVar((*string)(&cmd.priority), "priority", string(types.VirtualMachineMovePriorityDefaultPriority), "The task priority")
 }
 
@@ -81,9 +77,6 @@ func (cmd *migrate) Process(ctx context.Context) error {
 	if err := cmd.DatastoreFlag.Process(ctx); err != nil {
 		return err
 	}
-	if err := cmd.NetworkFlag.Process(ctx); err != nil {
-		return err
-	}
 
 	return nil
 }
@@ -102,36 +95,7 @@ Examples:
 }
 
 func (cmd *migrate) relocate(ctx context.Context, vm *object.VirtualMachine) error {
-	spec := cmd.spec
-
-	if cmd.NetworkFlag.IsSet() {
-		dev, err := cmd.NetworkFlag.Device()
-		if err != nil {
-			return err
-		}
-
-		devices, err := vm.Device(ctx)
-		if err != nil {
-			return err
-		}
-
-		net := devices.SelectByType((*types.VirtualEthernetCard)(nil))
-		if len(net) != 1 {
-			return fmt.Errorf("-net specified, but %s has %d nics", vm.Name(), len(net))
-		}
-		cmd.NetworkFlag.Change(net[0], dev)
-
-		spec.DeviceChange = append(spec.DeviceChange, &types.VirtualDeviceConfigSpec{
-			Device:    net[0],
-			Operation: types.VirtualDeviceConfigSpecOperationEdit,
-		})
-	}
-
-	if cmd.VirtualMachineFlag.Spec {
-		return cmd.VirtualMachineFlag.WriteAny(spec)
-	}
-
-	task, err := vm.Relocate(ctx, spec, cmd.priority)
+	task, err := vm.Relocate(ctx, cmd.spec, cmd.priority)
 	if err != nil {
 		return err
 	}
diff --git a/object/virtual_device_list.go b/object/virtual_device_list.go
index 92797dcd..295171af 100644
--- a/object/virtual_device_list.go
+++ b/object/virtual_device_list.go
@@ -361,6 +361,77 @@ func (l VirtualDeviceList) newNVMEBusNumber() int32 {
 	return -1
 }
 
+// FindSATAController will find the named SATA or AHCI controller if given, otherwise will pick an available controller.
+// An error is returned if the named controller is not found or not a SATA or AHCI controller. Or, if name is not
+// given and no available controller can be found.
+func (l VirtualDeviceList) FindSATAController(name string) (types.BaseVirtualController, error) {
+	if name != "" {
+		d := l.Find(name)
+		if d == nil {
+			return nil, fmt.Errorf("device '%s' not found", name)
+		}
+		switch c := d.(type) {
+		case *types.VirtualSATAController:
+			return c, nil
+		case *types.VirtualAHCIController:
+			return c, nil
+		default:
+			return nil, fmt.Errorf("%s is not a SATA or AHCI controller", name)
+		}
+	}
+
+	c := l.PickController((*types.VirtualSATAController)(nil))
+	if c == nil {
+		c = l.PickController((*types.VirtualAHCIController)(nil))
+	}
+	if c == nil {
+		return nil, errors.New("no available SATA or AHCI controller")
+	}
+
+	switch c := c.(type) {
+	case *types.VirtualSATAController:
+		return c, nil
+	case *types.VirtualAHCIController:
+		return c, nil
+	}
+
+	return nil, errors.New("unexpected controller type")
+}
+
+// CreateSATAController creates a new SATA controller.
+func (l VirtualDeviceList) CreateSATAController() (types.BaseVirtualDevice, error) {
+	sata := &types.VirtualAHCIController{}
+	sata.BusNumber = l.newSATABusNumber()
+	sata.Key = l.NewKey()
+
+	return sata, nil
+}
+
+var sataBusNumbers = []int{0, 1, 2, 3}
+
+// newSATABusNumber returns the bus number to use for adding a new SATA bus device.
+// -1 is returned if there are no bus numbers available.
+func (l VirtualDeviceList) newSATABusNumber() int32 {
+	var used []int
+
+	for _, d := range l.SelectByType((*types.VirtualSATAController)(nil)) {
+		num := d.(types.BaseVirtualController).GetVirtualController().BusNumber
+		if num >= 0 {
+			used = append(used, int(num))
+		} // else caller is creating a new vm using SATAControllerTypes
+	}
+
+	sort.Ints(used)
+
+	for i, n := range sataBusNumbers {
+		if i == len(used) || n != used[i] {
+			return int32(n)
+		}
+	}
+
+	return -1
+}
+
 // FindDiskController will find an existing ide or scsi disk controller.
 func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualController, error) {
 	switch {
@@ -370,6 +441,8 @@ func (l VirtualDeviceList) FindDiskController(name string) (types.BaseVirtualCon
 		return l.FindSCSIController("")
 	case name == "nvme":
 		return l.FindNVMEController("")
+	case name == "sata":
+		return l.FindSATAController("")
 	default:
 		if c, ok := l.Find(name).(types.BaseVirtualController); ok {
 			return c, nil
@@ -389,6 +462,8 @@ func (l VirtualDeviceList) PickController(kind types.BaseVirtualController) type
 			return num < 15
 		case *types.VirtualIDEController:
 			return num < 2
+		case types.BaseVirtualSATAController:
+			return num < 30
 		case *types.VirtualNVMEController:
 			return num < 8
 		default:
@@ -909,8 +984,6 @@ func (l VirtualDeviceList) Type(device types.BaseVirtualDevice) string {
 		return "pvscsi"
 	case *types.VirtualLsiLogicSASController:
 		return "lsilogic-sas"
-	case *types.VirtualNVMEController:
-		return "nvme"
 	case *types.VirtualPrecisionClock:
 		return "clock"
 	default:
diff --git a/object/vm_compatability_checker.go b/object/vm_compatability_checker.go
deleted file mode 100644
index 678b535d..00000000
--- a/object/vm_compatability_checker.go
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package object
-
-import (
-	"context"
-
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/methods"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-// VmCompatibilityChecker models the CompatibilityChecker, a singleton managed
-// object that can answer questions about compatibility of a virtual machine
-// with a host.
-//
-// For more information, see:
-// https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.vm.check.CompatibilityChecker.html
-type VmCompatibilityChecker struct {
-	Common
-}
-
-func NewVmCompatibilityChecker(c *vim25.Client) *VmCompatibilityChecker {
-	return &VmCompatibilityChecker{
-		Common: NewCommon(c, *c.ServiceContent.VmCompatibilityChecker),
-	}
-}
-
-func (c VmCompatibilityChecker) CheckCompatibility(
-	ctx context.Context,
-	vm types.ManagedObjectReference,
-	host *types.ManagedObjectReference,
-	pool *types.ManagedObjectReference,
-	testTypes ...string) ([]types.CheckResult, error) {
-
-	req := types.CheckCompatibility_Task{
-		This:     c.Reference(),
-		Vm:       vm,
-		Host:     host,
-		Pool:     pool,
-		TestType: testTypes,
-	}
-
-	res, err := methods.CheckCompatibility_Task(ctx, c.c, &req)
-	if err != nil {
-		return nil, err
-	}
-
-	ti, err := NewTask(c.c, res.Returnval).WaitForResult(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return ti.Result.(types.ArrayOfCheckResult).CheckResult, nil
-}
-
-func (c VmCompatibilityChecker) CheckVmConfig(
-	ctx context.Context,
-	spec types.VirtualMachineConfigSpec,
-	vm *types.ManagedObjectReference,
-	host *types.ManagedObjectReference,
-	pool *types.ManagedObjectReference,
-	testTypes ...string) ([]types.CheckResult, error) {
-
-	req := types.CheckVmConfig_Task{
-		This:     c.Reference(),
-		Spec:     spec,
-		Vm:       vm,
-		Host:     host,
-		Pool:     pool,
-		TestType: testTypes,
-	}
-
-	res, err := methods.CheckVmConfig_Task(ctx, c.c, &req)
-	if err != nil {
-		return nil, err
-	}
-
-	ti, err := NewTask(c.c, res.Returnval).WaitForResult(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return ti.Result.(types.ArrayOfCheckResult).CheckResult, nil
-}
diff --git a/object/vm_provisioning_checker.go b/object/vm_provisioning_checker.go
deleted file mode 100644
index 632f696c..00000000
--- a/object/vm_provisioning_checker.go
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package object
-
-import (
-	"context"
-
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/methods"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-// VmProvisioningChecker models the ProvisioningChecker, a singleton managed
-// object that can answer questions about the feasibility of certain
-// provisioning operations.
-//
-// For more information, see:
-// https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.vm.check.ProvisioningChecker.html
-type VmProvisioningChecker struct {
-	Common
-}
-
-func NewVmProvisioningChecker(c *vim25.Client) *VmProvisioningChecker {
-	return &VmProvisioningChecker{
-		Common: NewCommon(c, *c.ServiceContent.VmProvisioningChecker),
-	}
-}
-
-func (c VmProvisioningChecker) CheckRelocate(
-	ctx context.Context,
-	vm types.ManagedObjectReference,
-	spec types.VirtualMachineRelocateSpec,
-	testTypes ...string) ([]types.CheckResult, error) {
-
-	req := types.CheckRelocate_Task{
-		This:     c.Reference(),
-		Vm:       vm,
-		Spec:     spec,
-		TestType: testTypes,
-	}
-
-	res, err := methods.CheckRelocate_Task(ctx, c.c, &req)
-	if err != nil {
-		return nil, err
-	}
-
-	ti, err := NewTask(c.c, res.Returnval).WaitForResult(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return ti.Result.(types.ArrayOfCheckResult).CheckResult, nil
-}
diff --git a/ovf/importer/importer.go b/ovf/importer/importer.go
deleted file mode 100644
index 55382f06..00000000
--- a/ovf/importer/importer.go
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package importer
-
-import (
-	"bytes"
-	"context"
-	"errors"
-	"fmt"
-	"os"
-	"path"
-	"path/filepath"
-	"strings"
-
-	"github.com/vmware/govmomi/find"
-	"github.com/vmware/govmomi/nfc"
-	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/ovf"
-	"github.com/vmware/govmomi/vapi/library"
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/progress"
-	"github.com/vmware/govmomi/vim25/soap"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type Importer struct {
-	Log progress.LogFunc
-
-	Name           string
-	VerifyManifest bool
-	Hidden         bool
-
-	Client *vim25.Client
-	Finder *find.Finder
-	Sinker progress.Sinker
-
-	Datacenter   *object.Datacenter
-	Datastore    *object.Datastore
-	ResourcePool *object.ResourcePool
-	Host         *object.HostSystem
-	Folder       *object.Folder
-
-	Archive  Archive
-	Manifest map[string]*library.Checksum
-}
-
-func (imp *Importer) ReadManifest(fpath string) error {
-	base := filepath.Base(fpath)
-	ext := filepath.Ext(base)
-	mfName := strings.Replace(base, ext, ".mf", 1)
-
-	mf, _, err := imp.Archive.Open(mfName)
-	if err != nil {
-		msg := fmt.Sprintf("manifest %q: %s", mf, err)
-		fmt.Fprintln(os.Stderr, msg)
-		return errors.New(msg)
-	}
-	imp.Manifest, err = library.ReadManifest(mf)
-	_ = mf.Close()
-	return err
-}
-
-func (imp *Importer) Import(ctx context.Context, fpath string, opts Options) (*types.ManagedObjectReference, error) {
-
-	o, err := ReadOvf(fpath, imp.Archive)
-	if err != nil {
-		return nil, err
-	}
-
-	e, err := ReadEnvelope(o)
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse ovf: %s", err)
-	}
-
-	if e.VirtualSystem != nil {
-		if e.VirtualSystem != nil {
-			if opts.Name == nil {
-				opts.Name = &e.VirtualSystem.ID
-				if e.VirtualSystem.Name != nil {
-					opts.Name = e.VirtualSystem.Name
-				}
-			}
-		}
-		if imp.Hidden {
-			// TODO: userConfigurable is optional and defaults to false, so we should *add* userConfigurable=true
-			// if not set for a Property. But, there'd be a bunch more work involved to preserve other data in doing
-			// a complete xml.Marshal of the .ovf
-			o = bytes.ReplaceAll(o, []byte(`userConfigurable="false"`), []byte(`userConfigurable="true"`))
-		}
-	}
-
-	name := "Govc Virtual Appliance"
-	if opts.Name != nil {
-		name = *opts.Name
-	}
-
-	nmap, err := imp.NetworkMap(ctx, e, opts.NetworkMapping)
-	if err != nil {
-		return nil, err
-	}
-
-	cisp := types.OvfCreateImportSpecParams{
-		DiskProvisioning:   opts.DiskProvisioning,
-		EntityName:         name,
-		IpAllocationPolicy: opts.IPAllocationPolicy,
-		IpProtocol:         opts.IPProtocol,
-		OvfManagerCommonParams: types.OvfManagerCommonParams{
-			DeploymentOption: opts.Deployment,
-			Locale:           "US"},
-		PropertyMapping: OVFMap(opts.PropertyMapping),
-		NetworkMapping:  nmap,
-	}
-
-	m := ovf.NewManager(imp.Client)
-	spec, err := m.CreateImportSpec(ctx, string(o), imp.ResourcePool, imp.Datastore, cisp)
-	if err != nil {
-		return nil, err
-	}
-	if spec.Error != nil {
-		return nil, errors.New(spec.Error[0].LocalizedMessage)
-	}
-	if spec.Warning != nil {
-		for _, w := range spec.Warning {
-			_, _ = imp.Log(fmt.Sprintf("Warning: %s\n", w.LocalizedMessage))
-		}
-	}
-
-	if opts.Annotation != "" {
-		switch s := spec.ImportSpec.(type) {
-		case *types.VirtualMachineImportSpec:
-			s.ConfigSpec.Annotation = opts.Annotation
-		case *types.VirtualAppImportSpec:
-			s.VAppConfigSpec.Annotation = opts.Annotation
-		}
-	}
-
-	if imp.VerifyManifest {
-		if err := imp.ReadManifest(fpath); err != nil {
-			return nil, err
-		}
-	}
-
-	lease, err := imp.ResourcePool.ImportVApp(ctx, spec.ImportSpec, imp.Folder, imp.Host)
-	if err != nil {
-		return nil, err
-	}
-
-	info, err := lease.Wait(ctx, spec.FileItem)
-	if err != nil {
-		return nil, err
-	}
-
-	u := lease.StartUpdater(ctx, info)
-	defer u.Done()
-
-	for _, i := range info.Items {
-		if err := imp.Upload(ctx, lease, i); err != nil {
-			return nil, err
-		}
-	}
-
-	return &info.Entity, lease.Complete(ctx)
-}
-
-func (imp *Importer) NetworkMap(ctx context.Context, e *ovf.Envelope, networks []Network) ([]types.OvfNetworkMapping, error) {
-	var nmap []types.OvfNetworkMapping
-	for _, m := range networks {
-		if m.Network == "" {
-			continue // Not set, let vSphere choose the default network
-		}
-		if err := ValidateNetwork(e, m); err != nil && imp.Log != nil {
-			_, _ = imp.Log(err.Error() + "\n")
-		}
-
-		var ref types.ManagedObjectReference
-
-		net, err := imp.Finder.Network(ctx, m.Network)
-		if err != nil {
-			switch err.(type) {
-			case *find.NotFoundError:
-				if !ref.FromString(m.Network) {
-					return nil, err
-				} // else this is a raw MO ref
-			default:
-				return nil, err
-			}
-		} else {
-			ref = net.Reference()
-		}
-
-		nmap = append(nmap, types.OvfNetworkMapping{
-			Name:    m.Name,
-			Network: ref,
-		})
-	}
-
-	return nmap, nil
-}
-
-func OVFMap(op []Property) (p []types.KeyValue) {
-	for _, v := range op {
-		p = append(p, types.KeyValue{
-			Key:   v.Key,
-			Value: v.Value,
-		})
-	}
-
-	return
-}
-
-func ValidateNetwork(e *ovf.Envelope, net Network) error {
-	var names []string
-
-	if e.Network != nil {
-		for _, n := range e.Network.Networks {
-			if n.Name == net.Name {
-				return nil
-			}
-			names = append(names, n.Name)
-		}
-	}
-
-	return fmt.Errorf("warning: invalid NetworkMapping.Name=%q, valid names=%s", net.Name, names)
-}
-
-func ValidateChecksum(ctx context.Context, lease *nfc.Lease, sum *library.Checksum, file string, key string) error {
-	// Perform the checksum match eagerly, after each file upload, instead
-	// of after uploading all the files, to provide fail-fast behavior.
-	// (Trade-off here is multiple GetManifest() API calls to the server.)
-	manifests, err := lease.GetManifest(ctx)
-	if err != nil {
-		return err
-	}
-	for _, m := range manifests {
-		if m.Key == key {
-			// Compare server-side computed checksum of uploaded file
-			// against the client's manifest entry (assuming client's
-			// manifest has correct checksums - client doesn't compute
-			// checksum of the file before uploading).
-
-			// Try matching sha1 first (newer versions have moved to sha256).
-			if strings.ToUpper(sum.Algorithm) == "SHA1" {
-				if sum.Checksum != m.Sha1 {
-					msg := fmt.Sprintf("manifest checksum %v mismatch with uploaded checksum %v for file %v",
-						sum.Checksum, m.Sha1, file)
-					return errors.New(msg)
-				}
-				// Uploaded file checksum computed by server matches with local manifest entry.
-				return nil
-			}
-			// If not sha1, check for other types (in a separate field).
-			if !strings.EqualFold(sum.Algorithm, m.ChecksumType) {
-				msg := fmt.Sprintf("manifest checksum type %v mismatch with uploaded checksum type %v for file %v",
-					sum.Algorithm, m.ChecksumType, file)
-				return errors.New(msg)
-			}
-			if !strings.EqualFold(sum.Checksum, m.Checksum) {
-				msg := fmt.Sprintf("manifest checksum %v mismatch with uploaded checksum %v for file %v",
-					sum.Checksum, m.Checksum, file)
-				return errors.New(msg)
-			}
-			// Uploaded file checksum computed by server matches with local manifest entry.
-			return nil
-		}
-	}
-	msg := fmt.Sprintf("missing manifest entry on server for uploaded file %v (key %v), manifests=%#v", file, key, manifests)
-	return errors.New(msg)
-}
-
-func (imp *Importer) Upload(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error {
-	file := item.Path
-
-	f, size, err := imp.Archive.Open(file)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	logger := progress.NewProgressLogger(imp.Log, fmt.Sprintf("Uploading %s... ", path.Base(file)))
-	defer logger.Wait()
-
-	opts := soap.Upload{
-		ContentLength: size,
-		Progress:      logger,
-	}
-
-	err = lease.Upload(ctx, item, f, opts)
-	if err != nil {
-		return err
-	}
-
-	if imp.VerifyManifest {
-		mapImportKeyToKey := func(urls []types.HttpNfcLeaseDeviceUrl, importKey string) string {
-			for _, url := range urls {
-				if url.ImportKey == importKey {
-					return url.Key
-				}
-			}
-			return ""
-		}
-		leaseInfo, err := lease.Wait(ctx, nil)
-		if err != nil {
-			return err
-		}
-		sum, ok := imp.Manifest[file]
-		if !ok {
-			return fmt.Errorf("missing checksum for %v in manifest file", file)
-		}
-		return ValidateChecksum(ctx, lease, sum, file, mapImportKeyToKey(leaseInfo.DeviceUrl, item.DeviceId))
-	}
-	return nil
-}
diff --git a/ovf/importer/options.go b/ovf/importer/options.go
deleted file mode 100644
index cbb00af6..00000000
--- a/ovf/importer/options.go
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package importer
-
-import (
-	"encoding/json"
-
-	"github.com/vmware/govmomi/ovf"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type KeyValue struct {
-	Key   string
-	Value string
-}
-
-// case insensitive for Key + Value
-func (kv *KeyValue) UnmarshalJSON(b []byte) error {
-	e := struct {
-		types.KeyValue
-		Key   *string
-		Value *string
-	}{
-		types.KeyValue{}, &kv.Key, &kv.Value,
-	}
-
-	err := json.Unmarshal(b, &e)
-	if err != nil {
-		return err
-	}
-
-	if kv.Key == "" {
-		kv.Key = e.KeyValue.Key // "key"
-	}
-
-	if kv.Value == "" {
-		kv.Value = e.KeyValue.Value // "value"
-	}
-
-	return nil
-}
-
-type Property struct {
-	KeyValue
-	Spec *ovf.Property `json:",omitempty"`
-}
-
-type Network struct {
-	Name    string
-	Network string
-}
-
-type Options struct {
-	AllDeploymentOptions []string `json:",omitempty"`
-	Deployment           string   `json:",omitempty"`
-
-	AllDiskProvisioningOptions []string `json:",omitempty"`
-	DiskProvisioning           string
-
-	AllIPAllocationPolicyOptions []string `json:",omitempty"`
-	IPAllocationPolicy           string
-
-	AllIPProtocolOptions []string `json:",omitempty"`
-	IPProtocol           string
-
-	PropertyMapping []Property `json:",omitempty"`
-
-	NetworkMapping []Network `json:",omitempty"`
-
-	Annotation string `json:",omitempty"`
-
-	MarkAsTemplate bool
-	PowerOn        bool
-	InjectOvfEnv   bool
-	WaitForIP      bool
-	Name           *string
-}
diff --git a/ovf/importer/spec.go b/ovf/importer/spec.go
deleted file mode 100644
index a0028ec7..00000000
--- a/ovf/importer/spec.go
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package importer
-
-import (
-	"golang.org/x/text/cases"
-	"golang.org/x/text/language"
-
-	"github.com/vmware/govmomi/ovf"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-var (
-	allDiskProvisioningOptions = types.OvfCreateImportSpecParamsDiskProvisioningType("").Strings()
-
-	allIPAllocationPolicyOptions = types.VAppIPAssignmentInfoIpAllocationPolicy("").Strings()
-
-	allIPProtocolOptions = types.VAppIPAssignmentInfoProtocols("").Strings()
-)
-
-func SpecMap(e *ovf.Envelope, hidden, verbose bool) (res []Property) {
-	if e == nil || e.VirtualSystem == nil {
-		return nil
-	}
-
-	for _, p := range e.VirtualSystem.Product {
-		for i, v := range p.Property {
-			if v.UserConfigurable == nil {
-				continue
-			}
-			if !*v.UserConfigurable && !hidden {
-				continue
-			}
-
-			d := ""
-			if v.Default != nil {
-				d = *v.Default
-			}
-
-			// vSphere only accept True/False as boolean values for some reason
-			if v.Type == "boolean" {
-				d = cases.Title(language.Und).String(d)
-			}
-
-			np := Property{KeyValue: KeyValue{Key: p.Key(v), Value: d}}
-
-			if verbose {
-				np.Spec = &p.Property[i]
-			}
-
-			res = append(res, np)
-		}
-	}
-
-	return
-}
-
-func Spec(fpath string, a Archive, hidden, verbose bool) (*Options, error) {
-	e := &ovf.Envelope{}
-	if fpath != "" {
-		d, err := ReadOvf(fpath, a)
-		if err != nil {
-			return nil, err
-		}
-
-		if e, err = ReadEnvelope(d); err != nil {
-			return nil, err
-		}
-	}
-
-	var deploymentOptions []string
-	if e.DeploymentOption != nil && e.DeploymentOption.Configuration != nil {
-		// add default first
-		for _, c := range e.DeploymentOption.Configuration {
-			if c.Default != nil && *c.Default {
-				deploymentOptions = append(deploymentOptions, c.ID)
-			}
-		}
-
-		for _, c := range e.DeploymentOption.Configuration {
-			if c.Default == nil || !*c.Default {
-				deploymentOptions = append(deploymentOptions, c.ID)
-			}
-		}
-	}
-
-	o := Options{
-		DiskProvisioning:   allDiskProvisioningOptions[0],
-		IPAllocationPolicy: allIPAllocationPolicyOptions[0],
-		IPProtocol:         allIPProtocolOptions[0],
-		MarkAsTemplate:     false,
-		PowerOn:            false,
-		WaitForIP:          false,
-		InjectOvfEnv:       false,
-		PropertyMapping:    SpecMap(e, hidden, verbose),
-	}
-
-	if deploymentOptions != nil {
-		o.Deployment = deploymentOptions[0]
-	}
-
-	if e.VirtualSystem != nil && e.VirtualSystem.Annotation != nil {
-		for _, a := range e.VirtualSystem.Annotation {
-			o.Annotation += a.Annotation
-		}
-	}
-
-	if e.Network != nil {
-		for _, net := range e.Network.Networks {
-			o.NetworkMapping = append(o.NetworkMapping, Network{net.Name, ""})
-		}
-	}
-
-	if verbose {
-		if deploymentOptions != nil {
-			o.AllDeploymentOptions = deploymentOptions
-		}
-		o.AllDiskProvisioningOptions = allDiskProvisioningOptions
-		o.AllIPAllocationPolicyOptions = allIPAllocationPolicyOptions
-		o.AllIPProtocolOptions = allIPProtocolOptions
-	}
-
-	return &o, nil
-}
diff --git a/performance/manager.go b/performance/manager.go
index 5317304d..94b9c795 100644
--- a/performance/manager.go
+++ b/performance/manager.go
@@ -395,14 +395,14 @@ func (m *Manager) SampleByName(ctx context.Context, spec types.PerfQuerySpec, me
 
 // MetricSeries contains the same data as types.PerfMetricIntSeries, but with the CounterId converted to Name.
 type MetricSeries struct {
-	Name     string  `json:"name"`
-	Unit     string  `json:"unit"`
+	Name     string `json:"name"`
+	unit     string
 	Instance string  `json:"instance"`
 	Value    []int64 `json:"value"`
 }
 
 func (s *MetricSeries) Format(val int64) string {
-	switch types.PerformanceManagerUnit(s.Unit) {
+	switch types.PerformanceManagerUnit(s.unit) {
 	case types.PerformanceManagerUnitPercent:
 		return strconv.FormatFloat(float64(val)/100.0, 'f', 2, 64)
 	default:
@@ -470,7 +470,7 @@ func (m *Manager) ToMetricSeries(ctx context.Context, series []types.BasePerfEnt
 
 			values = append(values, MetricSeries{
 				Name:     info.Name(),
-				Unit:     info.UnitInfo.GetElementDescription().Key,
+				unit:     info.UnitInfo.GetElementDescription().Key,
 				Instance: v.Id.Instance,
 				Value:    v.Value,
 			})
diff --git a/scripts/license.sh b/scripts/license.sh
index a9b59e1c..7bb1302c 100755
--- a/scripts/license.sh
+++ b/scripts/license.sh
@@ -7,7 +7,7 @@ header_dir=$(dirname $0)/headers
 tmpfile=$(mktemp)
 trap "rm -f ${tmpfile}" EXIT
 
-git diff --name-status main | awk '{print $NF}' | while read file; do
+git diff --name-status main | awk '{print $2}' | while read file; do
   years=( $(git log --format='%ai' $file | cut -d- -f1 | sort -u) )
   num_years=${#years[@]}
 
diff --git a/simulator/esx/host_capability.go b/simulator/esx/host_capability.go
deleted file mode 100644
index d407cb6d..00000000
--- a/simulator/esx/host_capability.go
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package esx
-
-import "github.com/vmware/govmomi/vim25/types"
-
-// HostCapability captured via `govc object.collect -dump $host capability`
-var HostCapability = &types.HostCapability{
-	MaxSupportedVmMemory: 25149440, // 25TB since 7.0U1
-}
diff --git a/simulator/host_system.go b/simulator/host_system.go
index 2640e18e..367491a6 100644
--- a/simulator/host_system.go
+++ b/simulator/host_system.go
@@ -77,10 +77,6 @@ func NewHostSystem(host mo.HostSystem) *HostSystem {
 		info := *esx.HostHardwareInfo
 		hs.Hardware = &info
 	}
-	if hs.Capability == nil {
-		capability := *esx.HostCapability
-		hs.Capability = &capability
-	}
 
 	cfg := new(types.HostConfigInfo)
 	deepCopy(hs.Config, cfg)
diff --git a/simulator/model.go b/simulator/model.go
index 3a69e6ac..e80d0f30 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -232,48 +232,46 @@ func (*Model) fmtName(prefix string, num int) string {
 
 // kinds maps managed object types to their vcsim wrapper types
 var kinds = map[string]reflect.Type{
-	"AuthorizationManager":               reflect.TypeOf((*AuthorizationManager)(nil)).Elem(),
-	"ClusterComputeResource":             reflect.TypeOf((*ClusterComputeResource)(nil)).Elem(),
-	"CustomFieldsManager":                reflect.TypeOf((*CustomFieldsManager)(nil)).Elem(),
-	"CustomizationSpecManager":           reflect.TypeOf((*CustomizationSpecManager)(nil)).Elem(),
-	"Datacenter":                         reflect.TypeOf((*Datacenter)(nil)).Elem(),
-	"Datastore":                          reflect.TypeOf((*Datastore)(nil)).Elem(),
-	"DistributedVirtualPortgroup":        reflect.TypeOf((*DistributedVirtualPortgroup)(nil)).Elem(),
-	"DistributedVirtualSwitch":           reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
-	"DistributedVirtualSwitchManager":    reflect.TypeOf((*DistributedVirtualSwitchManager)(nil)).Elem(),
-	"EnvironmentBrowser":                 reflect.TypeOf((*EnvironmentBrowser)(nil)).Elem(),
-	"EventManager":                       reflect.TypeOf((*EventManager)(nil)).Elem(),
-	"ExtensionManager":                   reflect.TypeOf((*ExtensionManager)(nil)).Elem(),
-	"FileManager":                        reflect.TypeOf((*FileManager)(nil)).Elem(),
-	"Folder":                             reflect.TypeOf((*Folder)(nil)).Elem(),
-	"GuestOperationsManager":             reflect.TypeOf((*GuestOperationsManager)(nil)).Elem(),
-	"HostDatastoreBrowser":               reflect.TypeOf((*HostDatastoreBrowser)(nil)).Elem(),
-	"HostLocalAccountManager":            reflect.TypeOf((*HostLocalAccountManager)(nil)).Elem(),
-	"HostNetworkSystem":                  reflect.TypeOf((*HostNetworkSystem)(nil)).Elem(),
-	"HostCertificateManager":             reflect.TypeOf((*HostCertificateManager)(nil)).Elem(),
-	"HostSystem":                         reflect.TypeOf((*HostSystem)(nil)).Elem(),
-	"IpPoolManager":                      reflect.TypeOf((*IpPoolManager)(nil)).Elem(),
-	"LicenseManager":                     reflect.TypeOf((*LicenseManager)(nil)).Elem(),
-	"OptionManager":                      reflect.TypeOf((*OptionManager)(nil)).Elem(),
-	"OvfManager":                         reflect.TypeOf((*OvfManager)(nil)).Elem(),
-	"PerformanceManager":                 reflect.TypeOf((*PerformanceManager)(nil)).Elem(),
-	"PropertyCollector":                  reflect.TypeOf((*PropertyCollector)(nil)).Elem(),
-	"ResourcePool":                       reflect.TypeOf((*ResourcePool)(nil)).Elem(),
-	"SearchIndex":                        reflect.TypeOf((*SearchIndex)(nil)).Elem(),
-	"SessionManager":                     reflect.TypeOf((*SessionManager)(nil)).Elem(),
-	"StoragePod":                         reflect.TypeOf((*StoragePod)(nil)).Elem(),
-	"StorageResourceManager":             reflect.TypeOf((*StorageResourceManager)(nil)).Elem(),
-	"TaskManager":                        reflect.TypeOf((*TaskManager)(nil)).Elem(),
-	"TenantTenantManager":                reflect.TypeOf((*TenantManager)(nil)).Elem(),
-	"UserDirectory":                      reflect.TypeOf((*UserDirectory)(nil)).Elem(),
-	"VcenterVStorageObjectManager":       reflect.TypeOf((*VcenterVStorageObjectManager)(nil)).Elem(),
-	"ViewManager":                        reflect.TypeOf((*ViewManager)(nil)).Elem(),
-	"VirtualApp":                         reflect.TypeOf((*VirtualApp)(nil)).Elem(),
-	"VirtualDiskManager":                 reflect.TypeOf((*VirtualDiskManager)(nil)).Elem(),
-	"VirtualMachine":                     reflect.TypeOf((*VirtualMachine)(nil)).Elem(),
-	"VirtualMachineCompatibilityChecker": reflect.TypeOf((*VmCompatibilityChecker)(nil)).Elem(),
-	"VirtualMachineProvisioningChecker":  reflect.TypeOf((*VmProvisioningChecker)(nil)).Elem(),
-	"VmwareDistributedVirtualSwitch":     reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
+	"AuthorizationManager":            reflect.TypeOf((*AuthorizationManager)(nil)).Elem(),
+	"ClusterComputeResource":          reflect.TypeOf((*ClusterComputeResource)(nil)).Elem(),
+	"CustomFieldsManager":             reflect.TypeOf((*CustomFieldsManager)(nil)).Elem(),
+	"CustomizationSpecManager":        reflect.TypeOf((*CustomizationSpecManager)(nil)).Elem(),
+	"Datacenter":                      reflect.TypeOf((*Datacenter)(nil)).Elem(),
+	"Datastore":                       reflect.TypeOf((*Datastore)(nil)).Elem(),
+	"DistributedVirtualPortgroup":     reflect.TypeOf((*DistributedVirtualPortgroup)(nil)).Elem(),
+	"DistributedVirtualSwitch":        reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
+	"DistributedVirtualSwitchManager": reflect.TypeOf((*DistributedVirtualSwitchManager)(nil)).Elem(),
+	"EnvironmentBrowser":              reflect.TypeOf((*EnvironmentBrowser)(nil)).Elem(),
+	"EventManager":                    reflect.TypeOf((*EventManager)(nil)).Elem(),
+	"ExtensionManager":                reflect.TypeOf((*ExtensionManager)(nil)).Elem(),
+	"FileManager":                     reflect.TypeOf((*FileManager)(nil)).Elem(),
+	"Folder":                          reflect.TypeOf((*Folder)(nil)).Elem(),
+	"GuestOperationsManager":          reflect.TypeOf((*GuestOperationsManager)(nil)).Elem(),
+	"HostDatastoreBrowser":            reflect.TypeOf((*HostDatastoreBrowser)(nil)).Elem(),
+	"HostLocalAccountManager":         reflect.TypeOf((*HostLocalAccountManager)(nil)).Elem(),
+	"HostNetworkSystem":               reflect.TypeOf((*HostNetworkSystem)(nil)).Elem(),
+	"HostCertificateManager":          reflect.TypeOf((*HostCertificateManager)(nil)).Elem(),
+	"HostSystem":                      reflect.TypeOf((*HostSystem)(nil)).Elem(),
+	"IpPoolManager":                   reflect.TypeOf((*IpPoolManager)(nil)).Elem(),
+	"LicenseManager":                  reflect.TypeOf((*LicenseManager)(nil)).Elem(),
+	"OptionManager":                   reflect.TypeOf((*OptionManager)(nil)).Elem(),
+	"OvfManager":                      reflect.TypeOf((*OvfManager)(nil)).Elem(),
+	"PerformanceManager":              reflect.TypeOf((*PerformanceManager)(nil)).Elem(),
+	"PropertyCollector":               reflect.TypeOf((*PropertyCollector)(nil)).Elem(),
+	"ResourcePool":                    reflect.TypeOf((*ResourcePool)(nil)).Elem(),
+	"SearchIndex":                     reflect.TypeOf((*SearchIndex)(nil)).Elem(),
+	"SessionManager":                  reflect.TypeOf((*SessionManager)(nil)).Elem(),
+	"StoragePod":                      reflect.TypeOf((*StoragePod)(nil)).Elem(),
+	"StorageResourceManager":          reflect.TypeOf((*StorageResourceManager)(nil)).Elem(),
+	"TaskManager":                     reflect.TypeOf((*TaskManager)(nil)).Elem(),
+	"TenantTenantManager":             reflect.TypeOf((*TenantManager)(nil)).Elem(),
+	"UserDirectory":                   reflect.TypeOf((*UserDirectory)(nil)).Elem(),
+	"VcenterVStorageObjectManager":    reflect.TypeOf((*VcenterVStorageObjectManager)(nil)).Elem(),
+	"ViewManager":                     reflect.TypeOf((*ViewManager)(nil)).Elem(),
+	"VirtualApp":                      reflect.TypeOf((*VirtualApp)(nil)).Elem(),
+	"VirtualDiskManager":              reflect.TypeOf((*VirtualDiskManager)(nil)).Elem(),
+	"VirtualMachine":                  reflect.TypeOf((*VirtualMachine)(nil)).Elem(),
+	"VmwareDistributedVirtualSwitch":  reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
 }
 
 func loadObject(content types.ObjectContent) (mo.Reference, error) {
diff --git a/simulator/registry.go b/simulator/registry.go
index acfc2265..7c3c7776 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -572,16 +572,6 @@ func (r *Registry) TenantManager() *TenantManager {
 	return r.Get(r.content().TenantManager.Reference()).(*TenantManager)
 }
 
-// VmCompatibilityChecker returns VmCompatibilityChecker singleton
-func (r *Registry) VmCompatibilityChecker() *VmCompatibilityChecker {
-	return r.Get(r.content().VmCompatibilityChecker.Reference()).(*VmCompatibilityChecker)
-}
-
-// VmProvisioningChecker returns VmProvisioningChecker singleton
-func (r *Registry) VmProvisioningChecker() *VmProvisioningChecker {
-	return r.Get(r.content().VmProvisioningChecker.Reference()).(*VmProvisioningChecker)
-}
-
 // ExtensionManager returns the ExtensionManager singleton
 func (r *Registry) ExtensionManager() *ExtensionManager {
 	return r.Get(r.content().ExtensionManager.Reference()).(*ExtensionManager)
diff --git a/simulator/resource_pool.go b/simulator/resource_pool.go
index 0ecd6f6f..d34fa5b8 100644
--- a/simulator/resource_pool.go
+++ b/simulator/resource_pool.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -39,17 +39,6 @@ func asResourcePoolMO(obj mo.Reference) (*mo.ResourcePool, bool) {
 	return rp, ok
 }
 
-func resourcePoolHosts(ctx *Context, pool *ResourcePool) []types.ManagedObjectReference {
-	switch owner := ctx.Map.Get(pool.Owner).(type) {
-	case *ClusterComputeResource:
-		return owner.Host
-	case *mo.ComputeResource:
-		return owner.Host
-	default:
-		return nil
-	}
-}
-
 func NewResourcePool() *ResourcePool {
 	pool := &ResourcePool{
 		ResourcePool: esx.ResourcePool,
diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go
index c026070c..1a05352e 100644
--- a/simulator/virtual_machine.go
+++ b/simulator/virtual_machine.go
@@ -2345,11 +2345,6 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 			})
 		}
 
-		cspec := &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.DeviceChange}
-		if err := vm.configureDevices(ctx, cspec); err != nil {
-			return nil, err
-		}
-
 		ctx.postEvent(&types.VmMigratedEvent{
 			VmEvent:          vm.event(),
 			SourceHost:       *ctx.Map.Get(*vm.Runtime.Host).(*HostSystem).eventArgument(),
diff --git a/simulator/vm_compatibility_checker.go b/simulator/vm_compatibility_checker.go
deleted file mode 100644
index 398199d3..00000000
--- a/simulator/vm_compatibility_checker.go
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package simulator
-
-import (
-	"fmt"
-	"math/rand"
-	"slices"
-
-	"github.com/vmware/govmomi/vim25/methods"
-	"github.com/vmware/govmomi/vim25/mo"
-	"github.com/vmware/govmomi/vim25/soap"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type VmCompatibilityChecker struct {
-	mo.VirtualMachineCompatibilityChecker
-}
-
-func resolveHostsAndPool(ctx *Context, vm, host, pool *types.ManagedObjectReference) (*ResourcePool, []types.ManagedObjectReference) {
-	var vmMo *VirtualMachine
-	var poolMo *ResourcePool
-
-	switch {
-	case pool != nil:
-		poolMo = ctx.Map.Get(*pool).(*ResourcePool)
-	case vm != nil:
-		vmMo = ctx.Map.Get(*vm).(*VirtualMachine)
-		poolMo = ctx.Map.Get(*vmMo.ResourcePool).(*ResourcePool)
-	case host != nil:
-		h := ctx.Map.Get(*host).(*HostSystem)
-		parent := hostParent(&h.HostSystem).ResourcePool
-		poolMo = ctx.Map.Get(*parent).(*ResourcePool)
-	}
-
-	var hosts []types.ManagedObjectReference
-
-	switch {
-	case host != nil:
-		hosts = append(hosts, *host)
-	case pool != nil:
-		hosts = resourcePoolHosts(ctx, poolMo)
-	case vm != nil:
-		hosts = append(hosts, *vmMo.Runtime.Host)
-	}
-
-	return poolMo, hosts
-}
-
-func validateHostsAndPool(ctx *Context, pool *ResourcePool, hosts []types.ManagedObjectReference) *types.InvalidArgument {
-	allHosts := resourcePoolHosts(ctx, pool)
-
-	for _, host := range hosts {
-		if !slices.Contains(allHosts, host) {
-			return &types.InvalidArgument{
-				InvalidProperty: "spec.pool",
-			}
-		}
-	}
-
-	return nil
-}
-
-func (c *VmCompatibilityChecker) checkVmConfigSpec(ctx *Context, check *types.CheckResult, spec types.VirtualMachineConfigSpec, hosts []types.ManagedObjectReference) {
-	if check.Host == nil {
-		// By default all hosts use the same HostSystem template, so we check against any.
-		// But we could choose a host based on the spec, e.g. record + playback of real hosts
-		check.Host = &hosts[rand.Intn(len(hosts))]
-	}
-
-	host := ctx.Map.Get(*check.Host).(*HostSystem)
-
-	mem := int32(spec.MemoryMB)
-	if mem > 0 {
-		min := int32(4)
-		max := host.Capability.MaxSupportedVmMemory
-		if mem > max || mem < min {
-			check.Warning = append(check.Warning, types.LocalizedMethodFault{
-				Fault: &types.MemorySizeNotSupported{
-					MemorySizeMB:    mem,
-					MinMemorySizeMB: min,
-					MaxMemorySizeMB: max,
-				},
-				LocalizedMessage: fmt.Sprintf("vm requires %d MB of memory, outside the range of %d to %d", mem, min, max),
-			})
-		}
-	}
-
-	cpu := spec.NumCPUs
-	if cpu > 0 {
-		max := int32(host.Summary.Hardware.NumCpuCores)
-		if cpu > max {
-			check.Warning = append(check.Warning, types.LocalizedMethodFault{
-				Fault: &types.NotEnoughCpus{
-					NumCpuDest: max,
-					NumCpuVm:   cpu,
-				},
-				LocalizedMessage: fmt.Sprintf("vm requires %d CPUs, host has %d", cpu, max),
-			})
-		}
-	}
-
-	if spec.GuestId != "" {
-		var guest types.VirtualMachineGuestOsIdentifier
-		if !slices.Contains(guest.Strings(), spec.GuestId) {
-			check.Warning = append(check.Warning, types.LocalizedMethodFault{
-				Fault: &types.UnsupportedGuest{
-					UnsupportedGuestOS: spec.GuestId,
-				},
-				LocalizedMessage: fmt.Sprintf("vm guest os %s not supported", spec.GuestId),
-			})
-		}
-	}
-}
-
-func (c *VmCompatibilityChecker) CheckVmConfigTask(
-	ctx *Context,
-	r *types.CheckVmConfig_Task) soap.HasFault {
-
-	task := CreateTask(c, "checkVmConfig", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		if r.Vm == nil && r.Host == nil && r.Pool == nil {
-			return nil, new(types.InvalidArgument)
-		}
-
-		poolMo, hosts := resolveHostsAndPool(ctx, r.Vm, r.Host, r.Pool)
-		if err := validateHostsAndPool(ctx, poolMo, hosts); err != nil {
-			return nil, err
-		}
-
-		check := types.CheckResult{
-			Vm:   r.Vm,
-			Host: r.Host,
-		}
-
-		c.checkVmConfigSpec(ctx, &check, r.Spec, hosts)
-
-		return types.ArrayOfCheckResult{
-			CheckResult: []types.CheckResult{check},
-		}, nil
-	})
-
-	return &methods.CheckVmConfig_TaskBody{
-		Res: &types.CheckVmConfig_TaskResponse{
-			Returnval: task.Run(ctx),
-		},
-	}
-}
-
-func (c *VmCompatibilityChecker) CheckCompatibilityTask(
-	ctx *Context,
-	r *types.CheckCompatibility_Task) soap.HasFault {
-
-	task := CreateTask(c, "checkCompatibility", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		poolMo, hosts := resolveHostsAndPool(ctx, &r.Vm, r.Host, r.Pool)
-		if err := validateHostsAndPool(ctx, poolMo, hosts); err != nil {
-			return nil, err
-		}
-
-		check := types.CheckResult{
-			Vm:   &r.Vm,
-			Host: r.Host,
-		}
-
-		return types.ArrayOfCheckResult{
-			CheckResult: []types.CheckResult{check},
-		}, nil
-	})
-
-	return &methods.CheckCompatibility_TaskBody{
-		Res: &types.CheckCompatibility_TaskResponse{
-			Returnval: task.Run(ctx),
-		},
-	}
-}
diff --git a/simulator/vm_provisioning_checker.go b/simulator/vm_provisioning_checker.go
deleted file mode 100644
index 7634c7d3..00000000
--- a/simulator/vm_provisioning_checker.go
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package simulator
-
-import (
-	"github.com/vmware/govmomi/vim25/methods"
-	"github.com/vmware/govmomi/vim25/mo"
-	"github.com/vmware/govmomi/vim25/soap"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-type VmProvisioningChecker struct {
-	mo.VirtualMachineProvisioningChecker
-}
-
-func (c *VmProvisioningChecker) CheckRelocateTask(
-	ctx *Context,
-	r *types.CheckRelocate_Task) soap.HasFault {
-
-	task := CreateTask(c, "checkRelocate", func(t *Task) (types.AnyType, types.BaseMethodFault) {
-		check := types.CheckResult{
-			Vm: &r.Vm,
-		}
-
-		return types.ArrayOfCheckResult{
-			CheckResult: []types.CheckResult{check},
-		}, nil
-	})
-
-	return &methods.CheckRelocate_TaskBody{
-		Res: &types.CheckRelocate_TaskResponse{
-			Returnval: task.Run(ctx),
-		},
-	}
-}
diff --git a/vim25/progress/loger.go b/vim25/progress/loger.go
deleted file mode 100644
index b1356688..00000000
--- a/vim25/progress/loger.go
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package progress
-
-import (
-	"fmt"
-	"io"
-	"sync"
-	"time"
-)
-
-type LogFunc func(msg string) (int, error)
-
-type ProgressLogger struct {
-	log    LogFunc
-	prefix string
-
-	wg sync.WaitGroup
-
-	sink chan chan Report
-	done chan struct{}
-}
-
-func NewProgressLogger(log LogFunc, prefix string) *ProgressLogger {
-	p := &ProgressLogger{
-		log:    log,
-		prefix: prefix,
-
-		sink: make(chan chan Report),
-		done: make(chan struct{}),
-	}
-
-	p.wg.Add(1)
-
-	go p.loopA()
-
-	return p
-}
-
-// loopA runs before Sink() has been called.
-func (p *ProgressLogger) loopA() {
-	var err error
-
-	defer p.wg.Done()
-
-	tick := time.NewTicker(100 * time.Millisecond)
-	defer tick.Stop()
-
-	called := false
-
-	for stop := false; !stop; {
-		select {
-		case ch := <-p.sink:
-			err = p.loopB(tick, ch)
-			stop = true
-			called = true
-		case <-p.done:
-			stop = true
-		case <-tick.C:
-			line := fmt.Sprintf("\r%s", p.prefix)
-			p.log(line)
-		}
-	}
-
-	if err != nil && err != io.EOF {
-		p.log(fmt.Sprintf("\r%sError: %s\n", p.prefix, err))
-	} else if called {
-		p.log(fmt.Sprintf("\r%sOK\n", p.prefix))
-	}
-}
-
-// loopA runs after Sink() has been called.
-func (p *ProgressLogger) loopB(tick *time.Ticker, ch <-chan Report) error {
-	var r Report
-	var ok bool
-	var err error
-
-	for ok = true; ok; {
-		select {
-		case r, ok = <-ch:
-			if !ok {
-				break
-			}
-			err = r.Error()
-		case <-tick.C:
-			line := fmt.Sprintf("\r%s", p.prefix)
-			if r != nil {
-				line += fmt.Sprintf("(%.0f%%", r.Percentage())
-				detail := r.Detail()
-				if detail != "" {
-					line += fmt.Sprintf(", %s", detail)
-				}
-				line += ")"
-			}
-			p.log(line)
-		}
-	}
-
-	return err
-}
-
-func (p *ProgressLogger) Sink() chan<- Report {
-	ch := make(chan Report)
-	p.sink <- ch
-	return ch
-}
-
-func (p *ProgressLogger) Wait() {
-	close(p.done)
-	p.wg.Wait()
-}


Test patch:
diff --git a/.github/workflows/govmomi-govc-tests.yaml b/.github/workflows/govmomi-govc-tests.yaml
index 8569681b..f7a27c46 100644
--- a/.github/workflows/govmomi-govc-tests.yaml
+++ b/.github/workflows/govmomi-govc-tests.yaml
@@ -31,7 +31,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        go-version: ["1.21"]
+        go-version: ["1.20"]
         platform: ["ubuntu-20.04"]
         cmd: ["govc-test"]
         experimental: [false]
@@ -73,7 +73,7 @@ jobs:
     name: Verify govc docs are up2date
     strategy:
       matrix:
-        go-version: ["1.21"]
+        go-version: ["1.20"]
         platform: ["ubuntu-20.04"]
     runs-on: ${{ matrix.platform }}
     timeout-minutes: 3
diff --git a/ovf/importer/options_test.go b/govc/importx/options_test.go
similarity index 90%
rename from ovf/importer/options_test.go
rename to govc/importx/options_test.go
index d447215c..ac6e35ba 100644
--- a/ovf/importer/options_test.go
+++ b/govc/importx/options_test.go
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package importer_test
+package importx_test
 
 import (
 	"bytes"
 	"encoding/json"
 	"testing"
 
-	"github.com/vmware/govmomi/ovf/importer"
+	"github.com/vmware/govmomi/govc/importx"
 )
 
 func TestDecodeOptions(t *testing.T) {
@@ -52,7 +52,7 @@ func TestDecodeOptions(t *testing.T) {
   "Name": null
 }
 `)
-	var opts importer.Options
+	var opts importx.Options
 	err := json.NewDecoder(bytes.NewReader(spec)).Decode(&opts)
 	if err != nil {
 		t.Fatal(err)
diff --git a/govc/test/vm.bats b/govc/test/vm.bats
index da01c59b..85c0576d 100755
--- a/govc/test/vm.bats
+++ b/govc/test/vm.bats
@@ -959,15 +959,6 @@ load test_helper
 
   run govc object.collect -s "vm/new-folder/$vm" parent
   assert_success
-
-  run govc vm.info -r "$vm"
-  assert_matches "Network: +DC0_DVPG0"
-
-  run govc vm.migrate -host "$host0" -net "VM Network" "$vm"
-  assert_success
-
-  run govc vm.info -r "$vm"
-  assert_matches "Network: +VM Network"
 }
 
 @test "object name with slash" {
@@ -1241,86 +1232,3 @@ load test_helper
   run govc vm.customize -vm DC0_H0_VM0 -ip 10.0.0.45 -netmask 255.255.0.0 -type Linux
   assert_success
 }
-
-@test "vm.check.config" {
-  vcsim_env -cluster 2
-
-  export GOVC_SHOW_UNRELEASED=true
-
-  vm=DC0_C0_RP0_VM0
-  run govc vm.create -spec -pool DC0_C0/Resources $vm
-  assert_success
-  spec="$output"
-
-  run govc vm.check.config -vm $vm <<<"$spec"
-  assert_success
-
-  # we can use any host in the VM's cluster
-  for host in $(govc find /DC0/host/DC0_C0 -type f) ; do
-    run govc vm.check.config -host "$host" <<<"$spec"
-    assert_success
-  done
-
-  run govc vm.check.config -host DC0_C1_H0 <<<"$spec"
-  assert_failure # pool and host do not belong to the same compute resource
-
-  run govc vm.check.config -pool DC0_C1/Resources <<<"$spec"
-  assert_failure # pool and host do not belong to the same compute resource
-
-  # spec.memoryMB
-  max_mem=$(govc object.collect -s DC0_C1_H0 capability.maxSupportedVmMemory)
-  run govc vm.create -spec -m "$((max_mem+100))" $vm
-  assert_success
-  spec="$output"
-
-  run govc vm.check.config -vm $vm -json <<<"$spec"
-  assert_success
-  assert_matches "outside the range"
-
-  # spec.numCPUs
-  max_cpu=$(govc object.collect -s "$host" summary.hardware.numCpuCores)
-  run govc vm.create -spec -c "$((max_cpu+100))" $vm
-  assert_success
-  spec="$output"
-
-  run govc vm.check.config -vm $vm -json <<<"$spec"
-  assert_success
-  assert_matches "vm requires 100 CPUs"
-
-  # spec.guestId
-  run govc vm.create -spec -g ttylinux -pool DC0_C0/Resources $vm
-  assert_success
-  spec="$output"
-
-  run govc vm.check.config -vm $vm -json <<<"$spec"
-  assert_success
-  assert_matches unsupported
-}
-
-@test "vm.check.compat" {
-  vcsim_env -cluster 2
-
-  export GOVC_SHOW_UNRELEASED=true
-
-  vm=DC0_C0_RP0_VM0
-
-  run govc vm.check.compat -vm $vm -pool DC0_C0/Resources
-  assert_success
-
-  run govc vm.check.compat -vm $vm -pool DC0_C1/Resources
-  assert_failure # InvalidArgument spec.pool
-}
-
-@test "vm.check.relocate" {
-  vcsim_env -cluster 2
-
-  export GOVC_SHOW_UNRELEASED=true
-
-  vm=DC0_C0_RP0_VM0
-  run govc vm.migrate -spec -pool DC0_C0/Resources $vm
-  assert_success
-  spec="$output"
-
-  run govc vm.check.relocate -vm $vm <<<"$spec"
-  assert_success
-}
diff --git a/simulator/vm_compatibility_checker_test.go b/simulator/vm_compatibility_checker_test.go
deleted file mode 100644
index 20f9c07a..00000000
--- a/simulator/vm_compatibility_checker_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package simulator_test
-
-import (
-	"context"
-	"testing"
-
-	"github.com/vmware/govmomi/find"
-	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/simulator"
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-func TestVmCompatibilityChecker(t *testing.T) {
-	simulator.Test(func(ctx context.Context, c *vim25.Client) {
-		finder := find.NewFinder(c, true)
-		datacenter, err := finder.DefaultDatacenter(ctx)
-		if err != nil {
-			t.Fatalf("default datacenter not found: %s", err)
-		}
-		finder.SetDatacenter(datacenter)
-		vmList, err := finder.VirtualMachineList(ctx, "*")
-		if len(vmList) == 0 {
-			t.Fatal("vmList == 0")
-		}
-		vm := vmList[0]
-		vmRef := vm.Reference()
-
-		vmcc := object.NewVmCompatibilityChecker(c)
-
-		t.Run("CheckCompatibility", func(t *testing.T) {
-			results, err := vmcc.CheckCompatibility(
-				ctx,
-				vm.Reference(),
-				nil,
-				nil)
-
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			for _, result := range results {
-				if len(result.Error) > 0 {
-					t.Fatal("result has errors")
-				}
-				if len(result.Warning) > 0 {
-					t.Fatal("result has warnings")
-				}
-			}
-		})
-
-		t.Run("CheckVmConfig", func(t *testing.T) {
-			t.Run("for existing VM", func(t *testing.T) {
-				results, err := vmcc.CheckVmConfig(
-					ctx,
-					types.VirtualMachineConfigSpec{
-						NumCPUs: 2,
-					},
-					&vmRef,
-					nil,
-					nil)
-
-				if err != nil {
-					t.Fatal(err)
-				}
-
-				for _, result := range results {
-					if len(result.Error) > 0 {
-						t.Fatal("result has errors")
-					}
-					if len(result.Warning) > 0 {
-						t.Fatal("result has warnings")
-					}
-				}
-			})
-			t.Run("for new VM", func(t *testing.T) {
-				results, err := vmcc.CheckVmConfig(
-					ctx,
-					types.VirtualMachineConfigSpec{
-						Name:    "my-vm",
-						NumCPUs: 2,
-					},
-					&vmRef,
-					nil,
-					nil)
-
-				if err != nil {
-					t.Fatal(err)
-				}
-
-				for _, result := range results {
-					if len(result.Error) > 0 {
-						t.Fatal("result has errors")
-					}
-					if len(result.Warning) > 0 {
-						t.Fatal("result has warnings")
-					}
-				}
-			})
-		})
-	})
-}
diff --git a/simulator/vm_provisioning_checker_test.go b/simulator/vm_provisioning_checker_test.go
deleted file mode 100644
index df442b51..00000000
--- a/simulator/vm_provisioning_checker_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package simulator_test
-
-import (
-	"context"
-	"testing"
-
-	"github.com/vmware/govmomi/find"
-	"github.com/vmware/govmomi/object"
-	"github.com/vmware/govmomi/simulator"
-	"github.com/vmware/govmomi/vim25"
-	"github.com/vmware/govmomi/vim25/types"
-)
-
-func TestVmProvisioningChecker(t *testing.T) {
-	simulator.Test(func(ctx context.Context, c *vim25.Client) {
-		finder := find.NewFinder(c, true)
-		datacenter, err := finder.DefaultDatacenter(ctx)
-		if err != nil {
-			t.Fatalf("default datacenter not found: %s", err)
-		}
-		finder.SetDatacenter(datacenter)
-		vmList, err := finder.VirtualMachineList(ctx, "*")
-		if len(vmList) == 0 {
-			t.Fatal("vmList == 0")
-		}
-		vm := vmList[0]
-
-		vmpc := object.NewVmProvisioningChecker(c)
-
-		t.Run("CheckRelocate", func(t *testing.T) {
-			results, err := vmpc.CheckRelocate(
-				ctx,
-				vm.Reference(),
-				types.VirtualMachineRelocateSpec{})
-
-			for _, result := range results {
-				if err != nil {
-					t.Fatal(err)
-				}
-				if len(result.Error) > 0 {
-					t.Fatal("result has errors")
-				}
-				if len(result.Warning) > 0 {
-					t.Fatal("result has warnings")
-				}
-			}
-		})
-	})
-}