build(deps): bump the go-deps group across 1 directory with 8 updates

Bumps the go-deps group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [cloud.google.com/go/compute](https://github.com/googleapis/google-cloud-go) | `1.27.3` | `1.27.4` |
| [github.com/Azure/azure-sdk-for-go/sdk/storage/azblob](https://github.com/Azure/azure-sdk-for-go) | `1.3.2` | `1.4.0` |
| [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) | `1.54.18` | `1.55.2` |
| [github.com/gophercloud/gophercloud](https://github.com/gophercloud/gophercloud) | `1.13.0` | `1.14.0` |
| [github.com/openshift-online/ocm-sdk-go](https://github.com/openshift-online/ocm-sdk-go) | `0.1.429` | `0.1.432` |
| [github.com/osbuild/images](https://github.com/osbuild/images) | `0.70.0` | `0.72.0` |
| [github.com/vmware/govmomi](https://github.com/vmware/govmomi) | `0.38.0` | `0.39.0` |



Updates `cloud.google.com/go/compute` from 1.27.3 to 1.27.4
- [Release notes](https://github.com/googleapis/google-cloud-go/releases)
- [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-cloud-go/compare/compute/v1.27.3...compute/v1.27.4)

Updates `github.com/Azure/azure-sdk-for-go/sdk/storage/azblob` from 1.3.2 to 1.4.0
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/storage/azblob/v1.3.2...sdk/azcore/v1.4.0)

Updates `github.com/aws/aws-sdk-go` from 1.54.18 to 1.55.2
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.54.18...v1.55.2)

Updates `github.com/gophercloud/gophercloud` from 1.13.0 to 1.14.0
- [Release notes](https://github.com/gophercloud/gophercloud/releases)
- [Changelog](https://github.com/gophercloud/gophercloud/blob/v1.14.0/CHANGELOG.md)
- [Commits](https://github.com/gophercloud/gophercloud/compare/v1.13.0...v1.14.0)

Updates `github.com/openshift-online/ocm-sdk-go` from 0.1.429 to 0.1.432
- [Release notes](https://github.com/openshift-online/ocm-sdk-go/releases)
- [Changelog](https://github.com/openshift-online/ocm-sdk-go/blob/main/CHANGES.md)
- [Commits](https://github.com/openshift-online/ocm-sdk-go/compare/v0.1.429...v0.1.432)

Updates `github.com/osbuild/images` from 0.70.0 to 0.72.0
- [Release notes](https://github.com/osbuild/images/releases)
- [Commits](https://github.com/osbuild/images/compare/v0.70.0...v0.72.0)

Updates `github.com/vmware/govmomi` from 0.38.0 to 0.39.0
- [Release notes](https://github.com/vmware/govmomi/releases)
- [Changelog](https://github.com/vmware/govmomi/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vmware/govmomi/compare/v0.38.0...v0.39.0)

Updates `google.golang.org/api` from 0.188.0 to 0.189.0
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.188.0...v0.189.0)

---
updated-dependencies:
- dependency-name: cloud.google.com/go/compute
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-deps
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/storage/azblob
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-deps
- dependency-name: github.com/aws/aws-sdk-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-deps
- dependency-name: github.com/gophercloud/gophercloud
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-deps
- dependency-name: github.com/openshift-online/ocm-sdk-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-deps
- dependency-name: github.com/osbuild/images
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-deps
- dependency-name: github.com/vmware/govmomi
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-deps
- dependency-name: google.golang.org/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2024-07-25 04:38:39 +00:00 committed by Tomáš Hozza
parent fd71c9cefa
commit ca2c2dfa4f
104 changed files with 4713 additions and 2477 deletions

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
Copyright (c) 2014-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
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,6 +50,7 @@ type OutputFlag struct {
TTY bool
Dump bool
Out io.Writer
Spec bool
formatError bool
formatIndent bool
@ -72,6 +73,9 @@ 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
@ -159,6 +163,25 @@ 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
@ -204,7 +227,7 @@ type errorOutput struct {
}
func (e errorOutput) Write(w io.Writer) error {
reason := e.error.Error()
reason := e.Error()
var messages []string
var faults []types.LocalizableMessage
@ -261,15 +284,15 @@ func (e errorOutput) canEncode() bool {
return soap.IsSoapFault(e.error) || soap.IsVimFault(e.error)
}
// cannotEncode causes cli.Run to output err.Error() as it would without an error format specified
var cannotEncode = errors.New("cannot encode error")
// errCannotEncode causes cli.Run to output err.Error() as it would without an error format specified
var errCannotEncode = 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, cannotEncode
return nil, errCannotEncode
}
func (e errorOutput) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error {
@ -277,108 +300,9 @@ func (e errorOutput) MarshalXML(encoder *xml.Encoder, start xml.StartElement) er
if ok || e.canEncode() {
return encoder.Encode(e.error)
}
return cannotEncode
return errCannotEncode
}
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) *progressLogger {
return newProgressLogger(flag, prefix)
func (flag *OutputFlag) ProgressLogger(prefix string) *progress.ProgressLogger {
return progress.NewProgressLogger(flag.Log, prefix)
}

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2015-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2015-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
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,78 +26,12 @@ 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 Options
Options importer.Options
path string
}

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
Copyright (c) 2014-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
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,6 +22,7 @@ import (
"github.com/vmware/govmomi/govc/cli"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/ovf/importer"
"github.com/vmware/govmomi/vim25/types"
)
@ -43,21 +44,21 @@ func (cmd *ova) Run(ctx context.Context, f *flag.FlagSet) error {
return err
}
archive := &TapeArchive{Path: fpath}
archive.Client = cmd.Client
archive := &importer.TapeArchive{Path: fpath}
archive.Client = cmd.Importer.Client
cmd.Archive = archive
cmd.Importer.Archive = archive
moref, err := cmd.Import(fpath)
if err != nil {
return err
}
vm := object.NewVirtualMachine(cmd.Client, *moref)
vm := object.NewVirtualMachine(cmd.Importer.Client, *moref)
return cmd.Deploy(vm, cmd.OutputFlag)
}
func (cmd *ova) Import(fpath string) (*types.ManagedObjectReference, error) {
ovf := "*.ovf"
return cmd.ovfx.Import(ovf)
return cmd.Importer.Import(context.TODO(), ovf, cmd.Options)
}

View file

@ -1,5 +1,5 @@
/*
Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2014-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.
@ -17,23 +17,14 @@ 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"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/ovf/importer"
)
type ovfx struct {
@ -43,17 +34,9 @@ type ovfx struct {
*flags.ResourcePoolFlag
*flags.FolderFlag
*ArchiveFlag
*OptionsFlag
Name string
VerifyManifest bool
Hidden bool
Client *vim25.Client
Datacenter *object.Datacenter
Datastore *object.Datastore
ResourcePool *object.ResourcePool
Importer importer.Importer
}
func init() {
@ -72,14 +55,12 @@ 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.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")
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")
}
func (cmd *ovfx) Process(ctx context.Context) error {
@ -95,9 +76,6 @@ 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
}
@ -117,17 +95,17 @@ func (cmd *ovfx) Run(ctx context.Context, f *flag.FlagSet) error {
return err
}
archive := &FileArchive{Path: fpath}
archive.Client = cmd.Client
archive := &importer.FileArchive{Path: fpath}
archive.Client = cmd.Importer.Client
cmd.Archive = archive
cmd.Importer.Archive = archive
moref, err := cmd.Import(fpath)
moref, err := cmd.Importer.Import(context.TODO(), fpath, cmd.Options)
if err != nil {
return err
}
vm := object.NewVirtualMachine(cmd.Client, *moref)
vm := object.NewVirtualMachine(cmd.Importer.Client, *moref)
return cmd.Deploy(vm, cmd.OutputFlag)
}
@ -139,313 +117,70 @@ func (cmd *ovfx) Prepare(f *flag.FlagSet) (string, error) {
return "", errors.New("no file specified")
}
cmd.Client, err = cmd.DatastoreFlag.Client()
cmd.Importer.Log = cmd.OutputFlag.Log
cmd.Importer.Client, err = cmd.DatastoreFlag.Client()
if err != nil {
return "", err
}
cmd.Datacenter, err = cmd.DatastoreFlag.Datacenter()
cmd.Importer.Datacenter, err = cmd.DatastoreFlag.Datacenter()
if err != nil {
return "", err
}
cmd.Datastore, err = cmd.DatastoreFlag.Datastore()
cmd.Importer.Datastore, err = cmd.DatastoreFlag.Datastore()
if err != nil {
return "", err
}
cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePoolIfSpecified()
cmd.Importer.ResourcePool, err = cmd.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 nil, err
return "", err
}
if cmd.ResourcePool == nil {
if cmd.Importer.ResourcePool == nil {
if host == nil {
cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool()
cmd.Importer.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool()
} else {
cmd.ResourcePool, err = host.ResourcePool(ctx)
cmd.Importer.ResourcePool, err = host.ResourcePool(context.TODO())
}
if err != nil {
return nil, err
return "", err
}
}
m := ovf.NewManager(cmd.Client)
spec, err := m.CreateImportSpec(ctx, string(o), cmd.ResourcePool, cmd.Datastore, cisp)
cmd.Importer.Finder, err = cmd.DatastoreFlag.Finder()
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 {
_, _ = cmd.Log(fmt.Sprintf("Warning: %s\n", w.LocalizedMessage))
}
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
}
cmd.Importer.Host, err = cmd.HostSystemIfSpecified()
if err != nil {
return "", err
}
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.ResourcePool.Reference().Type != "VirtualApp" {
folder, err = cmd.FolderOrDefault("vm")
if cmd.Importer.ResourcePool.Reference().Type != "VirtualApp" {
cmd.Importer.Folder, err = cmd.FolderOrDefault("vm")
if err != nil {
return nil, err
return "", err
}
}
if cmd.VerifyManifest {
err = cmd.readManifest(fpath)
if err != nil {
return nil, err
if cmd.Importer.Name == "" {
// Override name from options if specified
if cmd.Options.Name != nil {
cmd.Importer.Name = *cmd.Options.Name
}
} else {
cmd.Options.Name = &cmd.Importer.Name
}
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)
return f.Arg(0), nil
}

View file

@ -22,27 +22,18 @@ import (
"fmt"
"io"
"path"
"strings"
"github.com/vmware/govmomi/govc/cli"
"github.com/vmware/govmomi/govc/flags"
"github.com/vmware/govmomi/ovf"
"github.com/vmware/govmomi/vim25/types"
)
var (
allDiskProvisioningOptions = types.OvfCreateImportSpecParamsDiskProvisioningType("").Strings()
allIPAllocationPolicyOptions = types.VAppIPAssignmentInfoIpAllocationPolicy("").Strings()
allIPProtocolOptions = types.VAppIPAssignmentInfoProtocols("").Strings()
"github.com/vmware/govmomi/ovf/importer"
)
type spec struct {
*ArchiveFlag
*flags.ClientFlag
*flags.OutputFlag
Archive importer.Archive
hidden bool
}
@ -51,8 +42,6 @@ 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)
@ -63,9 +52,6 @@ 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
}
@ -86,29 +72,29 @@ func (cmd *spec) Run(ctx context.Context, f *flag.FlagSet) error {
if len(fpath) > 0 {
switch path.Ext(fpath) {
case ".ovf":
cmd.Archive = &FileArchive{Path: fpath}
cmd.Archive = &importer.FileArchive{Path: fpath}
case "", ".ova":
cmd.Archive = &TapeArchive{Path: fpath}
cmd.Archive = &importer.TapeArchive{Path: fpath}
fpath = "*.ovf"
default:
return fmt.Errorf("invalid file extension %s", path.Ext(fpath))
}
if isRemotePath(f.Arg(0)) {
if importer.IsRemotePath(f.Arg(0)) {
client, err := cmd.Client()
if err != nil {
return err
}
switch archive := cmd.Archive.(type) {
case *FileArchive:
case *importer.FileArchive:
archive.Client = client
case *TapeArchive:
case *importer.TapeArchive:
archive.Client = client
}
}
}
env, err := cmd.Spec(fpath)
env, err := importer.Spec(fpath, cmd.Archive, cmd.hidden, cmd.Verbose())
if err != nil {
return err
}
@ -120,114 +106,9 @@ func (cmd *spec) Run(ctx context.Context, f *flag.FlagSet) error {
}
type specResult struct {
*Options
*importer.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
}

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
Copyright (c) 2016-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
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,6 +243,9 @@ 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{}
@ -471,6 +474,10 @@ 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

View file

@ -307,7 +307,7 @@ func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
if err != nil {
return err
}
if cmd.place {
if cmd.place || cmd.Spec {
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 {
if !cmd.force && !cmd.Spec {
vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name)
_, err := datastore.Stat(ctx, vmxPath)
@ -506,6 +506,10 @@ 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)
}
@ -519,6 +523,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 {

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Copyright (c) 2016-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
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,6 +32,7 @@ type migrate struct {
*flags.ResourcePoolFlag
*flags.HostSystemFlag
*flags.DatastoreFlag
*flags.NetworkFlag
*flags.VirtualMachineFlag
priority types.VirtualMachineMovePriority
@ -58,6 +59,9 @@ 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")
}
@ -77,6 +81,9 @@ 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
}
@ -95,7 +102,36 @@ Examples:
}
func (cmd *migrate) relocate(ctx context.Context, vm *object.VirtualMachine) error {
task, err := vm.Relocate(ctx, cmd.spec, cmd.priority)
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)
if err != nil {
return err
}

View file

@ -21,5 +21,5 @@ const (
ClientName = "govmomi"
// ClientVersion is the version of this SDK
ClientVersion = "0.38.0"
ClientVersion = "0.39.0"
)

View file

@ -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:

View file

@ -94,6 +94,33 @@ func (m VirtualDiskManager) CreateVirtualDisk(
return NewTask(m.c, res.Returnval), nil
}
// ExtendVirtualDisk extends an existing virtual disk.
func (m VirtualDiskManager) ExtendVirtualDisk(
ctx context.Context,
name string, datacenter *Datacenter,
capacityKb int64,
eagerZero *bool) (*Task, error) {
req := types.ExtendVirtualDisk_Task{
This: m.Reference(),
Name: name,
NewCapacityKb: capacityKb,
EagerZero: eagerZero,
}
if datacenter != nil {
ref := datacenter.Reference()
req.Datacenter = &ref
}
res, err := methods.ExtendVirtualDisk_Task(ctx, m.c, &req)
if err != nil {
return nil, err
}
return NewTask(m.c, res.Returnval), nil
}
// MoveVirtualDisk moves a virtual disk.
func (m VirtualDiskManager) MoveVirtualDisk(
ctx context.Context,

View file

@ -0,0 +1,111 @@
/*
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 ...types.CheckTestType) ([]types.CheckResult, error) {
req := types.CheckCompatibility_Task{
This: c.Reference(),
Vm: vm,
Host: host,
Pool: pool,
TestType: checkTestTypesToStrings(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 ...types.CheckTestType) ([]types.CheckResult, error) {
req := types.CheckVmConfig_Task{
This: c.Reference(),
Spec: spec,
Vm: vm,
Host: host,
Pool: pool,
TestType: checkTestTypesToStrings(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
}
func checkTestTypesToStrings(testTypes []types.CheckTestType) []string {
if len(testTypes) == 0 {
return nil
}
s := make([]string, len(testTypes))
for i := range testTypes {
s[i] = string(testTypes[i])
}
return s
}

View file

@ -0,0 +1,67 @@
/*
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 ...types.CheckTestType) ([]types.CheckResult, error) {
req := types.CheckRelocate_Task{
This: c.Reference(),
Vm: vm,
Spec: spec,
TestType: checkTestTypesToStrings(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
}

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
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
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,14 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package importx
package importer
import (
"archive/tar"
"bytes"
"context"
"errors"
"flag"
"fmt"
"io"
"net/url"
@ -31,32 +30,12 @@ import (
"strings"
"github.com/vmware/govmomi/ovf"
"github.com/vmware/govmomi/vapi/library"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
)
// 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)
func ReadOvf(fpath string, a Archive) ([]byte, error) {
r, _, err := a.Open(fpath)
if err != nil {
return nil, err
}
@ -65,7 +44,7 @@ func (f *ArchiveFlag) ReadOvf(fpath string) ([]byte, error) {
return io.ReadAll(r)
}
func (f *ArchiveFlag) ReadEnvelope(data []byte) (*ovf.Envelope, error) {
func 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)
@ -74,22 +53,6 @@ func (f *ArchiveFlag) 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)
}
@ -163,7 +126,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
}
@ -185,7 +148,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)

View file

@ -1,11 +1,11 @@
/*
Copyright (c) 2014 VMware, Inc. All Rights Reserved.
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
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 importx
package importer
import (
"fmt"

View file

@ -0,0 +1,326 @@
/*
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
}

View file

@ -0,0 +1,91 @@
/*
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
}

138
vendor/github.com/vmware/govmomi/ovf/importer/spec.go generated vendored Normal file
View file

@ -0,0 +1,138 @@
/*
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
}

View file

@ -21,10 +21,13 @@ import (
"fmt"
"net/url"
"path"
"strings"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vapi/library"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
@ -130,3 +133,50 @@ func (f *PathFinder) datastoreName(ctx context.Context, id string) (string, erro
f.cache[id] = name
return name, nil
}
// ResolveLibraryItemStorage transforms StorageURIs Datastore url (uuid) to Datastore name.
func (f *PathFinder) ResolveLibraryItemStorage(ctx context.Context, storage []library.Storage) error {
// TODO:
// - reuse PathFinder.cache
// - the transform here isn't Content Library specific, but is currently the only known use case
backing := map[string]*mo.Datastore{}
var ids []types.ManagedObjectReference
// don't think we can have more than 1 Datastore backing currently, future proof anyhow
for _, item := range storage {
id := item.StorageBacking.DatastoreID
if _, ok := backing[id]; ok {
continue
}
backing[id] = nil
ids = append(ids, types.ManagedObjectReference{Type: "Datastore", Value: id})
}
var ds []mo.Datastore
pc := property.DefaultCollector(f.c)
if err := pc.Retrieve(ctx, ids, []string{"name", "info.url"}, &ds); err != nil {
return err
}
for i := range ds {
backing[ds[i].Self.Value] = &ds[i]
}
for _, item := range storage {
b := backing[item.StorageBacking.DatastoreID]
dsurl := b.Info.GetDatastoreInfo().Url
for i := range item.StorageURIs {
u := strings.TrimPrefix(item.StorageURIs[i], dsurl)
u = strings.TrimPrefix(u, "/")
u = strings.SplitN(u, "?", 2)[0] // strip query, if any
item.StorageURIs[i] = (&object.DatastorePath{
Datastore: b.Name,
Path: u,
}).String()
}
}
return nil
}

View file

@ -35,9 +35,6 @@ type typeInfo struct {
// Map property names to field indices.
props map[string][]int
// Use base type for interface indices.
base bool
}
var typeInfoLock sync.RWMutex
@ -68,20 +65,22 @@ func typeInfoForType(tname string) *typeInfo {
func baseType(ftyp reflect.Type) reflect.Type {
base := strings.TrimPrefix(ftyp.Name(), "Base")
switch base {
case "MethodFault":
return nil
}
if kind, ok := types.TypeFunc()(base); ok {
return kind
}
return ftyp
return nil
}
func newTypeInfo(typ reflect.Type, base ...bool) *typeInfo {
func newTypeInfo(typ reflect.Type) *typeInfo {
t := typeInfo{
typ: typ,
props: make(map[string][]int),
}
if len(base) == 1 {
t.base = base[0]
}
t.build(typ, "", []int{})
return &t
@ -170,13 +169,16 @@ func (t *typeInfo) build(typ reflect.Type, fn string, fi []int) {
t.build(ftyp, fnc, fic)
}
// Base type can only access base fields, for example Datastore.Info
// is types.BaseDataStore, so we create a new(types.DatastoreInfo)
// Indexed property path may traverse into array element fields.
// When interface, use the base type to index fields.
// For example, BaseVirtualDevice:
// config.hardware.device[4000].deviceInfo.label
if t.base && ftyp.Kind() == reflect.Interface {
base := baseType(ftyp)
t.build(base, fnc, fic)
if ftyp.Kind() == reflect.Interface {
if base := baseType(ftyp); base != nil {
t.build(base, fnc, fic)
}
}
}
}
@ -283,7 +285,7 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value, field ...string)
item = reflect.New(rt.Elem())
}
field := newTypeInfo(item.Type(), true)
field := newTypeInfo(item.Type())
if ix, ok := field.props[path]; ok {
assignValue(item, ix, pv)
}

View file

@ -0,0 +1,125 @@
/*
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()
}

View file

@ -91361,7 +91361,7 @@ type VirtualMachineVirtualNuma struct {
// vNUMA node.
// If set to be non zero, VM uses the value as vNUMA node size.
// If unset, the VM continue to follow the behavior in last poweron.
CoresPerNumaNode int32 `xml:"coresPerNumaNode,omitempty" json:"coresPerNumaNode,omitempty"`
CoresPerNumaNode *int32 `xml:"coresPerNumaNode" json:"coresPerNumaNode,omitempty"`
// Capability to expose virtual NUMA when CPU hotadd is enabled.
//
// If set to true, ESXi will consider exposing virtual NUMA to
@ -91389,7 +91389,7 @@ type VirtualMachineVirtualNumaInfo struct {
// field should be ignored.
// In other cases, this field represents the virtual NUMA node size
// seen by the guest.
CoresPerNumaNode int32 `xml:"coresPerNumaNode,omitempty" json:"coresPerNumaNode,omitempty"`
CoresPerNumaNode *int32 `xml:"coresPerNumaNode" json:"coresPerNumaNode,omitempty"`
// Whether coresPerNode is determined automatically.
AutoCoresPerNumaNode *bool `xml:"autoCoresPerNumaNode" json:"autoCoresPerNumaNode,omitempty"`
// Whether virtual NUMA topology is exposed when CPU hotadd is

164
vendor/github.com/vmware/govmomi/vmdk/disk_info.go generated vendored Normal file
View file

@ -0,0 +1,164 @@
/*
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 vmdk
import (
"context"
"fmt"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)
type VirtualDiskInfo struct {
CapacityInBytes int64
DeviceKey int32
FileName string
Size int64
UniqueSize int64
}
// GetVirtualDiskInfoByUUID returns information about a virtual disk identified
// by the provided UUID. This method is valid for the following backing types:
//
// - VirtualDiskFlatVer2BackingInfo
// - VirtualDiskSeSparseBackingInfo
// - VirtualDiskRawDiskMappingVer1BackingInfo
// - VirtualDiskSparseVer2BackingInfo
// - VirtualDiskRawDiskVer2BackingInfo
//
// These are the only backing types that have a UUID property for comparing the
// provided value.
func GetVirtualDiskInfoByUUID(
ctx context.Context,
client *vim25.Client,
mo mo.VirtualMachine,
fetchProperties bool,
diskUUID string) (VirtualDiskInfo, error) {
if diskUUID == "" {
return VirtualDiskInfo{}, fmt.Errorf("diskUUID is empty")
}
switch {
case fetchProperties,
mo.Config == nil,
mo.Config.Hardware.Device == nil,
mo.LayoutEx == nil,
mo.LayoutEx.Disk == nil,
mo.LayoutEx.File == nil:
if ctx == nil {
return VirtualDiskInfo{}, fmt.Errorf("ctx is nil")
}
if client == nil {
return VirtualDiskInfo{}, fmt.Errorf("client is nil")
}
obj := object.NewVirtualMachine(client, mo.Self)
if err := obj.Properties(
ctx,
mo.Self,
[]string{"config", "layoutEx"},
&mo); err != nil {
return VirtualDiskInfo{},
fmt.Errorf("failed to retrieve properties: %w", err)
}
}
// Find the disk by UUID by inspecting all of the disk backing types that
// can have an associated UUID.
var (
disk *types.VirtualDisk
fileName string
)
for i := range mo.Config.Hardware.Device {
switch tvd := mo.Config.Hardware.Device[i].(type) {
case *types.VirtualDisk:
switch tb := tvd.Backing.(type) {
case *types.VirtualDiskFlatVer2BackingInfo:
if tb.Uuid == diskUUID {
disk = tvd
fileName = tb.FileName
}
case *types.VirtualDiskSeSparseBackingInfo:
if tb.Uuid == diskUUID {
disk = tvd
fileName = tb.FileName
}
case *types.VirtualDiskRawDiskMappingVer1BackingInfo:
if tb.Uuid == diskUUID {
disk = tvd
fileName = tb.FileName
}
case *types.VirtualDiskSparseVer2BackingInfo:
if tb.Uuid == diskUUID {
disk = tvd
fileName = tb.FileName
}
case *types.VirtualDiskRawDiskVer2BackingInfo:
if tb.Uuid == diskUUID {
disk = tvd
fileName = tb.DescriptorFileName
}
}
}
}
if disk == nil {
return VirtualDiskInfo{},
fmt.Errorf("disk not found with uuid %q", diskUUID)
}
// Build a lookup table for determining if file key belongs to this disk
// chain.
diskFileKeys := map[int32]struct{}{}
for i := range mo.LayoutEx.Disk {
if d := mo.LayoutEx.Disk[i]; d.Key == disk.Key {
for j := range d.Chain {
for k := range d.Chain[j].FileKey {
diskFileKeys[d.Chain[j].FileKey[k]] = struct{}{}
}
}
}
}
// Sum the disk's total size and unique size.
var (
size int64
uniqueSize int64
)
for i := range mo.LayoutEx.File {
f := mo.LayoutEx.File[i]
if _, ok := diskFileKeys[f.Key]; ok {
size += f.Size
uniqueSize += f.UniqueSize
}
}
return VirtualDiskInfo{
CapacityInBytes: disk.CapacityInBytes,
DeviceKey: disk.Key,
FileName: fileName,
Size: size,
UniqueSize: uniqueSize,
}, nil
}

View file

@ -10,7 +10,7 @@ You may obtain a copy of the License at
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.
nSee the License for the specific language governing permissions and
See the License for the specific language governing permissions and
limitations under the License.
*/
@ -41,8 +41,8 @@ var (
ErrInvalidFormat = errors.New("vmdk: invalid format (must be streamOptimized)")
)
// info is used to inspect a vmdk and generate an ovf template
type info struct {
// Info is used to inspect a vmdk and generate an ovf template
type Info struct {
Header struct {
MagicNumber uint32
Version uint32
@ -56,15 +56,15 @@ type info struct {
ImportName string
}
// stat looks at the vmdk header to make sure the format is streamOptimized and
// Stat looks at the vmdk header to make sure the format is streamOptimized and
// extracts the disk capacity required to properly generate the ovf descriptor.
func stat(name string) (*info, error) {
func Stat(name string) (*Info, error) {
f, err := os.Open(filepath.Clean(name))
if err != nil {
return nil, err
}
var di info
var di Info
var buf bytes.Buffer
@ -174,8 +174,8 @@ var ovfenv = `<?xml version="1.0" encoding="UTF-8"?>
</VirtualSystem>
</Envelope>`
// ovf returns an expanded descriptor template
func (di *info) ovf() (string, error) {
// OVF returns an expanded descriptor template
func (di *Info) OVF() (string, error) {
var buf bytes.Buffer
tmpl, err := template.New("ovf").Parse(ovfenv)
@ -209,7 +209,7 @@ func Import(ctx context.Context, c *vim25.Client, name string, datastore *object
m := ovf.NewManager(c)
fm := datastore.NewFileManager(p.Datacenter, p.Force)
disk, err := stat(name)
disk, err := Stat(name)
if err != nil {
return err
}
@ -245,7 +245,7 @@ func Import(ctx context.Context, c *vim25.Client, name string, datastore *object
}
// Expand the ovf template
descriptor, err := disk.ovf()
descriptor, err := disk.OVF()
if err != nil {
return err
}