Skip to content

Commit

Permalink
Added Instant Clone feature
Browse files Browse the repository at this point in the history
Resolves: vmware#1392
  • Loading branch information
brian57860 committed Jul 29, 2020
1 parent 0614f98 commit 85e4896
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 0 deletions.
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

0 comments on commit 85e4896

Please sign in to comment.