#!/usr/bin/env ruby
# frozen_string_literal: true

# Copyright (c) 2011-2017 VMware, Inc.  All Rights Reserved.
# SPDX-License-Identifier: MIT

require 'optimist'
require 'rbvmomi'
require 'rbvmomi/optimist'

VIM = RbVmomi::VIM

opts = Optimist.options do
  banner <<~EOS
    Clone a VM.

    Usage:
        clone_vm.rb [options] source_vm dest_vm

    VIM connection options:
    EOS

  rbvmomi_connection_opts

  text <<~EOS

    VM location options:
    EOS

  rbvmomi_datacenter_opt

  text <<~EOS

    Other options:
    EOS

  opt :linked_clone, 'Use a linked clone instead of a full clone'
end

Optimist.die('must specify host') unless opts[:host]
ARGV.size == 2 or abort 'must specify VM source name and VM target name'
vm_source = ARGV[0]
vm_target = ARGV[1]

vim = VIM.connect opts
dc = vim.serviceInstance.find_datacenter(opts[:datacenter]) or abort 'datacenter not found'
vm = dc.find_vm(vm_source) or abort 'VM not found'

if opts[:linked_clone]
  # The API for linked clones is quite strange. We can't create a linked
  # straight from any VM. The disks of the VM for which we can create a
  # linked clone need to be read-only and thus VC demands that the VM we
  # are cloning from uses delta-disks. Only then it will allow us to
  # share the base disk.
  #
  # Thus, this code first create a delta disk on top of the base disk for
  # the to-be-cloned VM, if delta disks aren't used already.
  disks = vm.config.hardware.device.grep(VIM::VirtualDisk)
  disks.select { |x| x.backing.parent == nil }.each do |disk|
    spec = {
      deviceChange: [
        {
          operation: :remove,
          device: disk
        },
        {
          operation: :add,
          fileOperation: :create,
          device: disk.dup.tap { |x|
            x.backing = x.backing.dup
            x.backing.fileName = "[#{disk.backing.datastore.name}]"
            x.backing.parent = disk.backing
          },
        }
      ]
    }
    vm.ReconfigVM_Task(spec: spec).wait_for_completion
  end

  relocateSpec = VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking)
else
  relocateSpec = VIM.VirtualMachineRelocateSpec
end

spec = VIM.VirtualMachineCloneSpec(location: relocateSpec,
                                   powerOn: false,
                                   template: false)

vm.CloneVM_Task(folder: vm.parent, name: vm_target, spec: spec).wait_for_completion
