vsphere/resource_vsphere_virtual_machine_migrate.go
func migrateVSphereVirtualMachineStateV2(is *terraform.InstanceState, meta interface{}) error {
	// All we really preserve from the old state is the UUID of the virtual
	// machine. We leverage some of the special parts of the import functionality
	// - namely validating disks, and flagging the VM as imported in the state to
	// guard against someone adding customization to the configuration and
	// accidentally forcing a new resource. To assist with the migration of state
	// from V1 to V3 as well, we now pull in disk attribute data that is
	// populated during the import process.
	//
	// Read will handle most of the population post-migration as it does for
	// import, and there will be an unavoidable diff for TF-only options on the
	// next plan. This diff should not require a reconfigure of the virtual
	// machine.
	client := meta.(*VSphereClient).vimClient
	name := is.ID
	id := is.Attributes["uuid"]
	if id == "" {
		return fmt.Errorf("resource ID %s has no UUID. State cannot be migrated", name)
	}

	log.Printf("[DEBUG] Migrate state for VM resource %q: UUID %q", name, id)
	vm, err := virtualmachine.FromUUID(client, id)
	if err != nil {
		return fmt.Errorf("error fetching virtual machine: %s", err)
	}
	props, err := virtualmachine.Properties(vm)
	if err != nil {
		return fmt.Errorf("error fetching virtual machine properties: %s", err)
	}

	// Validate the disks in the VM to make sure that they will work with the new
	// version of the resource. This is mainly ensuring that all disks are SCSI
	// disks.
	//
	// NOTE: This uses the current version of the resource to make this check,
	// which at some point in time may end up being a higher schema version than
	// version 2. At this point in time, there is nothing here that would cause
	// issues (nothing in the sub-resource read logic is reliant on schema
	// versions), and an empty ResourceData is sent anyway.
	diskCnt, _ := strconv.Atoi(is.Attributes["disk.#"])
	maxBus := diskCnt / 15
	l := object.VirtualDeviceList(props.Config.Hardware.Device)
	for k, v := range is.Attributes {
		if !regexp.MustCompile("disk\\.[0-9]+\\.key").MatchString(k) {
			continue
		}
		key, _ := strconv.Atoi(v)
		if key < 1 {
			continue
		}
		device := l.FindByKey(int32(key))
		if device == nil {
			continue
		}
		ctlr := l.FindByKey(device.GetVirtualDevice().ControllerKey)
		if ctlr == nil {
			continue
		}
		if sc, ok := ctlr.(types.BaseVirtualSCSIController); ok && sc.GetVirtualSCSIController().BusNumber > int32(maxBus) {
			maxBus = int(sc.GetVirtualSCSIController().BusNumber)
		}
	}

	d := resourceVSphereVirtualMachine().Data(&terraform.InstanceState{})
	d.Set("scsi_controller_count", maxBus+1)
	if err := virtualdevice.DiskImportOperation(d, client, object.VirtualDeviceList(props.Config.Hardware.Device)); err != nil {
		return err
	}

	rs := resourceVSphereVirtualMachine().Schema
	var guestNetTimeout string
	switch is.Attributes["wait_for_guest_net"] {
	case "false":
		guestNetTimeout = "-1"
	default:
		guestNetTimeout = fmt.Sprintf("%v", rs["wait_for_guest_net_timeout"].Default)
	}

	// The VM should be ready for reading now
	is.Attributes = make(map[string]string)
	is.ID = id
	is.Attributes["imported"] = "true"

	// Set some defaults. This helps possibly prevent diffs where these values
	// have not been changed.
	is.Attributes["scsi_controller_count"] = fmt.Sprintf("%v", rs["scsi_controller_count"].Default)
	is.Attributes["force_power_off"] = fmt.Sprintf("%v", rs["force_power_off"].Default)
	is.Attributes["migrate_wait_timeout"] = fmt.Sprintf("%v", rs["migrate_wait_timeout"].Default)
	is.Attributes["shutdown_wait_timeout"] = fmt.Sprintf("%v", rs["shutdown_wait_timeout"].Default)
	is.Attributes["wait_for_guest_net_timeout"] = guestNetTimeout
	is.Attributes["wait_for_guest_net_routable"] = fmt.Sprintf("%v", rs["wait_for_guest_net_routable"].Default)
	is.Attributes["scsi_controller_count"] = fmt.Sprintf("%v", maxBus+1)

	// Populate our disk data from the fake state.
	d.SetId(id)
	for k, v := range d.State().Attributes {
		if strings.HasPrefix(k, "disk.") {
			is.Attributes[k] = v
		}
	}

	log.Printf("[DEBUG] %s: Migration to V2 complete", resourceVSphereVirtualMachineIDString(d))
	return nil
}
func migrateVSphereVirtualMachineStateV2(is *terraform.InstanceState, meta interface{}) error {
	// All we really preserve from the old state is the UUID of the virtual
	// machine. We leverage some of the special parts of the import functionality
	// - namely validating disks, and flagging the VM as imported in the state to
	// guard against someone adding customization to the configuration and
	// accidentally forcing a new resource. To assist with the migration of state
	// from V1 to V3 as well, we now pull in disk attribute data that is
	// populated during the import process.
	//
	// Read will handle most of the population post-migration as it does for
	// import, and there will be an unavoidable diff for TF-only options on the
	// next plan. This diff should not require a reconfigure of the virtual
	// machine.
	client := meta.(*VSphereClient).vimClient
	name := is.ID
	id := is.Attributes["uuid"]
	if id == "" {
		return fmt.Errorf("resource ID %s has no UUID. State cannot be migrated", name)
	}

	log.Printf("[DEBUG] Migrate state for VM resource %q: UUID %q", name, id)
	vm, err := virtualmachine.FromUUID(client, id)
	if err != nil {
		return fmt.Errorf("error fetching virtual machine: %s", err)
	}
	props, err := virtualmachine.Properties(vm)
	if err != nil {
		return fmt.Errorf("error fetching virtual machine properties: %s", err)
	}

	// Validate the disks in the VM to make sure that they will work with the new
	// version of the resource. This is mainly ensuring that all disks are SCSI
	// disks.
	//
	// NOTE: This uses the current version of the resource to make this check,
	// which at some point in time may end up being a higher schema version than
	// version 2. At this point in time, there is nothing here that would cause
	// issues (nothing in the sub-resource read logic is reliant on schema
	// versions), and an empty ResourceData is sent anyway.
	diskCnt, _ := strconv.Atoi(is.Attributes["disk.#"])
	maxBus := diskCnt / 15
	l := object.VirtualDeviceList(props.Config.Hardware.Device)
	for k, v := range is.Attributes {
		if !regexp.MustCompile("disk\\.[0-9]+\\.key").MatchString(k) {
			continue
		}
		key, _ := strconv.Atoi(v)
		if key < 1 {
			continue
		}
		device := l.FindByKey(int32(key))
		if device == nil {
			continue
		}
		ctlr := l.FindByKey(device.GetVirtualDevice().ControllerKey)
		if ctlr == nil {
			continue
		}
		if sc, ok := ctlr.(types.BaseVirtualSCSIController); ok && sc.GetVirtualSCSIController().BusNumber > int32(maxBus) {
			maxBus = int(sc.GetVirtualSCSIController().BusNumber)
		}
	}

	d := resourceVSphereVirtualMachine().Data(&terraform.InstanceState{})
	d.Set("scsi_controller_count", maxBus+1)
	if err := virtualdevice.DiskImportOperation(d, client, object.VirtualDeviceList(props.Config.Hardware.Device)); err != nil {
		return err
	}

	rs := resourceVSphereVirtualMachine().Schema
	var guestNetTimeout string
	switch is.Attributes["wait_for_guest_net"] {
	case "false":
		guestNetTimeout = "-1"
	default:
		guestNetTimeout = fmt.Sprintf("%v", rs["wait_for_guest_net_timeout"].Default)
	}

	// The VM should be ready for reading now
	is.Attributes = make(map[string]string)
	is.ID = id
	is.Attributes["imported"] = "true"

	// Set some defaults. This helps possibly prevent diffs where these values
	// have not been changed.
	is.Attributes["scsi_controller_count"] = fmt.Sprintf("%v", rs["scsi_controller_count"].Default)
	is.Attributes["force_power_off"] = fmt.Sprintf("%v", rs["force_power_off"].Default)
	is.Attributes["migrate_wait_timeout"] = fmt.Sprintf("%v", rs["migrate_wait_timeout"].Default)
	is.Attributes["shutdown_wait_timeout"] = fmt.Sprintf("%v", rs["shutdown_wait_timeout"].Default)
	is.Attributes["wait_for_guest_net_timeout"] = guestNetTimeout
	is.Attributes["wait_for_guest_net_routable"] = fmt.Sprintf("%v", rs["wait_for_guest_net_routable"].Default)
	is.Attributes["scsi_controller_count"] = fmt.Sprintf("%v", maxBus+1)

	// Populate our disk data from the fake state.
	d.SetId(id)
	for k, v := range d.State().Attributes {
		if strings.HasPrefix(k, "disk.") {
			is.Attributes[k] = v
		}
	}

	log.Printf("[DEBUG] %s: Migration to V2 complete", resourceVSphereVirtualMachineIDString(d))
	return nil
}
// migrateVSphereVirtualMachineStateV2 migrates the state of the
// vsphere_virtual_machine from version 1 to version 2.