Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Instant Clone feature #1558

Merged
merged 1 commit into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions govc/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ but appear via `govc $cmd -h`:
- [vm.disk.create](#vmdiskcreate)
- [vm.guest.tools](#vmguesttools)
- [vm.info](#vminfo)
- [vm.instantclone](#vminstantclone)
- [vm.ip](#vmip)
- [vm.keystrokes](#vmkeystrokes)
- [vm.markastemplate](#vmmarkastemplate)
Expand Down Expand Up @@ -4830,6 +4831,50 @@ Options:
-waitip=false Wait for VM to acquire IP address
```

## vm.instantclone

```
Usage: govc vm.instantclone [OPTIONS] NAME

Instant Clone VM to NAME.

Examples:
govc vm.instantclone -vm source-vm new-vm
# Configure ExtraConfig variables on a guest VM:
govc vm.instantclone -vm source-vm -e guestinfo.ipaddress=192.168.0.1 -e guestinfo.netmask=255.255.255.0 new-vm
# Read the variable set above inside the guest:
vmware-rpctool "info-get guestinfo.ipaddress"
vmware-rpctool "info-get guestinfo.netmask"

Options:
-cert= Certificate [GOVC_CERTIFICATE]
-dc= Datacenter [GOVC_DATACENTER]
-debug=false Store debug logs [GOVC_DEBUG]
-ds= Datastore [GOVC_DATASTORE]
-dump=false Enable Go output
-e=[] ExtraConfig. <key>=<value>
-folder= Inventory folder [GOVC_FOLDER]
-json=false Enable JSON output
-k=false Skip verification of server certificate [GOVC_INSECURE]
-key= Private key [GOVC_PRIVATE_KEY]
-net= Network [GOVC_NETWORK]
-net.adapter=e1000 Network adapter type
-net.address= Network hardware address
-persist-session=true Persist session to disk [GOVC_PERSIST_SESSION]
-pool= Resource pool [GOVC_RESOURCE_POOL]
-tls-ca-certs= TLS CA certificates file [GOVC_TLS_CA_CERTS]
-tls-known-hosts= TLS known hosts file [GOVC_TLS_KNOWN_HOSTS]
-u= ESX or vCenter URL [GOVC_URL]
-vim-namespace=vim25 Vim namespace [GOVC_VIM_NAMESPACE]
-vim-version=6.7 Vim version [GOVC_VIM_VERSION]
-vm= Virtual machine [GOVC_VM]
-vm.dns= Find VM by FQDN
-vm.ip= Find VM by IP address
-vm.ipath= Find VM by inventory path
-vm.path= Find VM by path to .vmx file
-vm.uuid= Find VM by UUID
```

## vm.ip

```
Expand Down
4 changes: 4 additions & 0 deletions govc/flags/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func (f *DatastoreFlag) Process(ctx context.Context) error {
})
}

func (flag *DatastoreFlag) IsSet() bool {
return flag.Name != ""
}

func (f *DatastoreFlag) Args(args []string) []object.DatastorePath {
var files []object.DatastorePath

Expand Down
4 changes: 4 additions & 0 deletions govc/flags/folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ func (flag *FolderFlag) Process(ctx context.Context) error {
})
}

func (flag *FolderFlag) IsSet() bool {
return flag.name != ""
}

func (flag *FolderFlag) Folder() (*object.Folder, error) {
if flag.folder != nil {
return flag.folder, nil
Expand Down
4 changes: 4 additions & 0 deletions govc/flags/resource_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func (flag *ResourcePoolFlag) Process(ctx context.Context) error {
})
}

func (flag *ResourcePoolFlag) IsSet() bool {
return flag.name != ""
}

func (flag *ResourcePoolFlag) ResourcePool() (*object.ResourcePool, error) {
if flag.pool != nil {
return flag.pool, nil
Expand Down
250 changes: 250 additions & 0 deletions govc/vm/instantclone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/*
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

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 vm

import (
"context"
"flag"
"fmt"

"github.com/vmware/govmomi/govc/cli"
"github.com/vmware/govmomi/govc/flags"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/types"
)

type instantclone struct {
*flags.ClientFlag
*flags.DatacenterFlag
*flags.DatastoreFlag
*flags.ResourcePoolFlag
*flags.NetworkFlag
*flags.FolderFlag
*flags.VirtualMachineFlag

name string
extraConfig extraConfig

Client *vim25.Client
Datacenter *object.Datacenter
Datastore *object.Datastore
ResourcePool *object.ResourcePool
Folder *object.Folder
VirtualMachine *object.VirtualMachine
}

func init() {
cli.Register("vm.instantclone", &instantclone{})
}

func (cmd *instantclone) Register(ctx context.Context, f *flag.FlagSet) {
cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
cmd.ClientFlag.Register(ctx, f)

cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
cmd.DatacenterFlag.Register(ctx, f)

cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx)
cmd.DatastoreFlag.Register(ctx, f)

cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx)
cmd.ResourcePoolFlag.Register(ctx, f)

cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx)
cmd.NetworkFlag.Register(ctx, f)

cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
cmd.FolderFlag.Register(ctx, f)

cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
cmd.VirtualMachineFlag.Register(ctx, f)

f.Var(&cmd.extraConfig, "e", "ExtraConfig. <key>=<value>")
}

func (cmd *instantclone) Usage() string {
return "NAME"
}

func (cmd *instantclone) Description() string {
return `Instant Clone VM to NAME.

Examples:
govc vm.instantclone -vm source-vm new-vm
# Configure ExtraConfig variables on a guest VM:
govc vm.instantclone -vm source-vm -e guestinfo.ipaddress=192.168.0.1 -e guestinfo.netmask=255.255.255.0 new-vm
# Read the variable set above inside the guest:
vmware-rpctool "info-get guestinfo.ipaddress"
vmware-rpctool "info-get guestinfo.netmask"`
}

func (cmd *instantclone) Process(ctx context.Context) error {
if err := cmd.ClientFlag.Process(ctx); err != nil {
return err
}
if err := cmd.DatacenterFlag.Process(ctx); err != nil {
return err
}
if err := cmd.DatastoreFlag.Process(ctx); err != nil {
return err
}
if err := cmd.ResourcePoolFlag.Process(ctx); err != nil {
return err
}
if err := cmd.NetworkFlag.Process(ctx); err != nil {
return err
}
if err := cmd.FolderFlag.Process(ctx); err != nil {
return err
}
if err := cmd.VirtualMachineFlag.Process(ctx); err != nil {
return err
}

return nil
}

func (cmd *instantclone) Run(ctx context.Context, f *flag.FlagSet) error {
var err error

if len(f.Args()) != 1 {
return flag.ErrHelp
}

cmd.name = f.Arg(0)
if cmd.name == "" {
return flag.ErrHelp
}

cmd.Client, err = cmd.ClientFlag.Client()
if err != nil {
return err
}

cmd.Datacenter, err = cmd.DatacenterFlag.Datacenter()
if err != nil {
return err
}

cmd.Datastore, err = cmd.DatastoreFlag.Datastore()
if err != nil {
return err
}

cmd.Folder, err = cmd.FolderFlag.Folder()
if err != nil {
return err
}

cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool()
if err != nil {
return err
}

cmd.VirtualMachine, err = cmd.VirtualMachineFlag.VirtualMachine()
if err != nil {
return err
}

if cmd.VirtualMachine == nil {
return flag.ErrHelp
}

_, err = cmd.instantcloneVM(ctx)
if err != nil {
return err
}

return nil
}

func (cmd *instantclone) instantcloneVM(ctx context.Context) (*object.VirtualMachine, error) {
relocateSpec := types.VirtualMachineRelocateSpec{}

if cmd.NetworkFlag.IsSet() {
devices, err := cmd.VirtualMachine.Device(ctx)
if err != nil {
return nil, err
}

// prepare virtual device config spec for network card
configSpecs := []types.BaseVirtualDeviceConfigSpec{}

op := types.VirtualDeviceConfigSpecOperationAdd
card, derr := cmd.NetworkFlag.Device()
if derr != nil {
return nil, derr
}
// search for the first network card of the source
for _, device := range devices {
if _, ok := device.(types.BaseVirtualEthernetCard); ok {
op = types.VirtualDeviceConfigSpecOperationEdit
// set new backing info
cmd.NetworkFlag.Change(device, card)
card = device
break
}
}

configSpecs = append(configSpecs, &types.VirtualDeviceConfigSpec{
Operation: op,
Device: card,
})

relocateSpec.DeviceChange = configSpecs
}

if cmd.FolderFlag.IsSet() {
folderref := cmd.Folder.Reference()
relocateSpec.Folder = &folderref
}

if cmd.ResourcePoolFlag.IsSet() {
poolref := cmd.ResourcePool.Reference()
relocateSpec.Pool = &poolref
}

if cmd.DatastoreFlag.IsSet() {
datastoreref := cmd.Datastore.Reference()
relocateSpec.Datastore = &datastoreref
}

instantcloneSpec := &types.VirtualMachineInstantCloneSpec{
Name: cmd.name,
Location: relocateSpec,
}

if len(cmd.extraConfig) > 0 {
instantcloneSpec.Config = cmd.extraConfig
}

task, err := cmd.VirtualMachine.InstantClone(ctx, *instantcloneSpec)
if err != nil {
return nil, err
}

logger := cmd.ProgressLogger(fmt.Sprintf("Instant Cloning %s to %s...", cmd.VirtualMachine.InventoryPath, cmd.name))
defer logger.Wait()

info, err := task.WaitForResult(ctx, logger)
if err != nil {
return nil, err
}

return object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)), nil
}
14 changes: 14 additions & 0 deletions object/virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,20 @@ func (v VirtualMachine) Clone(ctx context.Context, folder *Folder, name string,
return NewTask(v.c, res.Returnval), nil
}

func (v VirtualMachine) InstantClone(ctx context.Context, config types.VirtualMachineInstantCloneSpec) (*Task, error) {
req := types.InstantClone_Task{
This: v.Reference(),
Spec: config,
}

res, err := methods.InstantClone_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}

return NewTask(v.c, res.Returnval), nil
}

func (v VirtualMachine) Customize(ctx context.Context, spec types.CustomizationSpec) (*Task, error) {
req := types.CustomizeVM_Task{
This: v.Reference(),
Expand Down