vsphere/internal/helper/virtualmachine/virtual_machine_helper.go
func ShutdownGuest(client *govmomi.Client, vm *object.VirtualMachine, timeout int) error {
	log.Printf("[DEBUG] Attempting guest shutdown of virtual machine %q", vm.InventoryPath)
	sctx, scancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
	defer scancel()
	if err := vm.ShutdownGuest(sctx); err != nil {
		return err
	}

	// We now wait on VM power state to be powerOff, via a property collector that waits on power state.
	p := client.PropertyCollector()
	if timeout < 1 {
		timeout = 1
	}
	pctx, pcancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
	defer pcancel()

	err := property.Wait(pctx, p, vm.Reference(), []string{"runtime.powerState"}, func(pc []types.PropertyChange) bool {
		for _, c := range pc {
			if c.Op != types.PropertyChangeOpAssign {
				continue
			}

			switch v := c.Val.(type) {
			case types.VirtualMachinePowerState:
				if v == types.VirtualMachinePowerStatePoweredOff {
					return true
				}
			}
		}

		return false
	})

	if err != nil {
		// Provide a friendly error message if we timed out waiting for a shutdown.
		if pctx.Err() == context.DeadlineExceeded {
			return errGuestShutdownTimeout
		}
		return err
	}
	return nil
}
func ShutdownGuest(client *govmomi.Client, vm *object.VirtualMachine, timeout int) error {
	log.Printf("[DEBUG] Attempting guest shutdown of virtual machine %q", vm.InventoryPath)
	sctx, scancel := context.WithTimeout(context.Background(), provider.DefaultAPITimeout)
	defer scancel()
	if err := vm.ShutdownGuest(sctx); err != nil {
		return err
	}

	// We now wait on VM power state to be powerOff, via a property collector that waits on power state.
	p := client.PropertyCollector()
	if timeout < 1 {
		timeout = 1
	}
	pctx, pcancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
	defer pcancel()

	err := property.Wait(pctx, p, vm.Reference(), []string{"runtime.powerState"}, func(pc []types.PropertyChange) bool {
		for _, c := range pc {
			if c.Op != types.PropertyChangeOpAssign {
				continue
			}

			switch v := c.Val.(type) {
			case types.VirtualMachinePowerState:
				if v == types.VirtualMachinePowerStatePoweredOff {
					return true
				}
			}
		}

		return false
	})

	if err != nil {
		// Provide a friendly error message if we timed out waiting for a shutdown.
		if pctx.Err() == context.DeadlineExceeded {
			return errGuestShutdownTimeout
		}
		return err
	}
	return nil
}
// ShutdownGuest wraps the graceful shutdown of a guest VM, and then waiting an
// appropriate amount of time for the guest power state to go to powered off.
// If the VM does not power off in the shutdown period specified by timeout (in
// minutes), an error is returned.
//
// The minimum value for timeout is 1 minute - setting to a 0 or negative value
// is not allowed and will just reset the timeout to the minimum.