diff --git a/tf-vsphere-devrc.mk.example b/tf-vsphere-devrc.mk.example index eb75534a3..a8e7f2a7d 100644 --- a/tf-vsphere-devrc.mk.example +++ b/tf-vsphere-devrc.mk.example @@ -22,6 +22,7 @@ export VSPHERE_ALLOW_UNVERIFIED_SSL ?= false # The following variables are shared across various tests. To ensure all tests # succeed, it's probably best to set all of these to valid values. export VSPHERE_TEMPLATE ?= base-linux # VM template to clone +export VSPHERE_TEMPLATE_WINDOWS ?= base-win # Windows VM template export VSPHERE_NETWORK_LABEL ?= vm-network # Port group label export VSPHERE_NETWORK_LABEL_DHCP ?= vm-network # Port group label for DHCP export VSPHERE_IPV4_ADDRESS ?= 10.0.0.100 # Customization IP address diff --git a/vsphere/event_helper.go b/vsphere/event_helper.go new file mode 100644 index 000000000..202fa0fc5 --- /dev/null +++ b/vsphere/event_helper.go @@ -0,0 +1,129 @@ +package vsphere + +import ( + "context" + "errors" + "fmt" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/event" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" +) + +// A list of known event IDs that we use for querying events. +const ( + eventTypeCustomizationSucceeded = "CustomizationSucceeded" +) + +// virtualMachineCustomizationWaiter is an object that waits for customization +// of a VirtualMachine to complete, by watching for success or failure events. +// +// The waiter should be created with newWaiter **before** the start of the +// customization task to be 100% certain that completion events are not missed. +type virtualMachineCustomizationWaiter struct { + // This channel will be closed upon completion, and should be blocked on. + done chan struct{} + + // Any error received from the waiter - be it the customization failure + // itself, timeouts waiting for the completion events, or other API-related + // errors. This will always be nil until done is closed. + err error +} + +// Done returns the done channel. This channel will be closed upon completion, +// and should be blocked on. +func (w *virtualMachineCustomizationWaiter) Done() chan struct{} { + return w.done +} + +// Err returns any error received from the waiter. This will always be nil +// until the channel returned by Done is closed. +func (w *virtualMachineCustomizationWaiter) Err() error { + return w.err +} + +// newVirtualMachineCustomizationWaiter returns a new +// virtualMachineCustomizationWaiter to use to wait for customization on. +// +// This should be called **before** the start of the customization task to be +// 100% certain that completion events are not missed. +func newVirtualMachineCustomizationWaiter(client *govmomi.Client, vm *object.VirtualMachine) *virtualMachineCustomizationWaiter { + w := &virtualMachineCustomizationWaiter{ + done: make(chan struct{}), + } + go func() { + w.err = w.wait(client, vm) + close(w.done) + }() + return w +} + +// wait waits for the customization of a supplied VirtualMachine to complete, +// either due to success or error. It does this by watching specifically for +// CustomizationSucceeded and CustomizationFailed events. If the customization +// failed due to some sort of error, the full formatted message is returned as +// an error. +func (w *virtualMachineCustomizationWaiter) wait(client *govmomi.Client, vm *object.VirtualMachine) error { + // Our listener loop callback. + success := make(chan struct{}) + cb := func(obj types.ManagedObjectReference, page []types.BaseEvent) error { + for _, be := range page { + switch e := be.(type) { + case *types.CustomizationFailed: + return errors.New(e.GetEvent().FullFormattedMessage) + case *types.CustomizationSucceeded: + close(success) + } + } + return nil + } + + mgr := event.NewManager(client.Client) + mgrErr := make(chan error, 1) + // Make a proper background context so that we can gracefully cancel the + // subscriber when we are done with it. This eventually gets passed down to + // the property collector SOAP calls. + pctx, pcancel := context.WithCancel(context.Background()) + defer pcancel() + go func() { + mgrErr <- mgr.Events(pctx, []types.ManagedObjectReference{vm.Reference()}, 10, true, false, cb) + }() + + // This is our waiter. We want to wait on all of these conditions. We also + // use a different context so that we can give a better error message on + // timeout without interfering with the subscriber's context. + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + select { + case err := <-mgrErr: + return err + case <-ctx.Done(): + if ctx.Err() == context.DeadlineExceeded { + return fmt.Errorf("timeout waiting for customization to complete") + } + case <-success: + // Pass case to break to success + } + return nil +} + +// selectEventsForReference allows you to query events for a specific +// ManagedObjectReference. +// +// Event types can be supplied to this function via the eventTypes parameter. +// This is highly recommended when you expect the list of events to be large, +// as there is no limit on returned events. +func selectEventsForReference(client *govmomi.Client, ref types.ManagedObjectReference, eventTypes []string) ([]types.BaseEvent, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + filter := types.EventFilterSpec{ + Entity: &types.EventFilterSpecByEntity{ + Entity: ref, + Recursion: types.EventFilterSpecRecursionOptionAll, + }, + EventTypeId: eventTypes, + } + mgr := event.NewManager(client.Client) + return mgr.QueryEvents(ctx, filter) +} diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 680e68d04..be5f9d033 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -235,6 +235,12 @@ func resourceVSphereVirtualMachine() *schema.Resource { Default: false, }, + "wait_for_guest_net": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "enable_disk_uuid": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -695,6 +701,16 @@ func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{ log.Printf("[ERROR] %s", err) return err } + + // Wait for VM guest networking before returning, so that Read can get + // accurate networking info for the state. + if d.Get("wait_for_guest_net").(bool) { + log.Printf("[DEBUG] Waiting for routeable guest network access") + if err := waitForGuestVMNet(client, vm); err != nil { + return err + } + log.Printf("[DEBUG] Guest has routeable network access.") + } } return resourceVSphereVirtualMachineRead(d, meta) @@ -961,6 +977,24 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ d.SetId(vm.Path()) log.Printf("[INFO] Created virtual machine: %s", d.Id()) + newVM, err := virtualMachineFromManagedObjectID(client, vm.moid) + if err != nil { + return err + } + newProps, err := virtualMachineProperties(newVM) + if err != nil { + return err + } + + if newProps.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn && d.Get("wait_for_guest_net").(bool) { + // We also need to wait for the guest networking to ensure an accurate set + // of information can be read into state and reported to the provisioners. + log.Printf("[DEBUG] Waiting for routeable guest network access") + if err := waitForGuestVMNet(client, newVM); err != nil { + return err + } + log.Printf("[DEBUG] Guest has routeable network access.") + } return resourceVSphereVirtualMachineRead(d, meta) } @@ -987,23 +1021,6 @@ func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) log.Printf("[DEBUG] Set the moid: %#v", vm.Reference().Value) } - state, err := vm.PowerState(context.TODO()) - if err != nil { - return err - } - - if state == types.VirtualMachinePowerStatePoweredOn { - // wait for interfaces to appear - log.Printf("[DEBUG] Waiting for interfaces to appear") - - _, err = vm.WaitForNetIP(context.TODO(), false) - if err != nil { - return err - } - - log.Printf("[DEBUG] Successfully waited for interfaces to appear") - } - var mvm mo.VirtualMachine collector := property.DefaultCollector(client.Client) if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore", "config", "runtime"}, &mvm); err != nil { @@ -1775,6 +1792,7 @@ func createCdroms(client *govmomi.Client, vm *object.VirtualMachine, datacenter } func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { + var cw *virtualMachineCustomizationWaiter dc, err := getDatacenter(c, vm.datacenter) if err != nil { @@ -2204,6 +2222,7 @@ func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { log.Printf("[DEBUG] custom spec: %v", customSpec) log.Printf("[DEBUG] VM customization starting") + cw = newVirtualMachineCustomizationWaiter(c, newVM) taskb, err := newVM.Customize(context.TODO(), customSpec) if err != nil { return err @@ -2212,7 +2231,6 @@ func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { if err != nil { return err } - log.Printf("[DEBUG] VM customization finished") } if vm.hasBootableVmdk || vm.template != "" { @@ -2228,9 +2246,22 @@ func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { if err != nil { return err } + if cw != nil { + // Customization is not yet done here 100%. We need to wait for the + // customization completion events to confirm, so start listening for those + // now. + <-cw.Done() + if cw.Err() != nil { + return cw.Err() + } + log.Printf("[DEBUG] VM customization finished") + } } + + vm.moid = newVM.Reference().Value return nil } + func getNetworkName(c *govmomi.Client, vm *object.VirtualMachine, nic types.BaseVirtualEthernetCard) (string, error) { backingInfo := nic.GetVirtualEthernetCard().Backing var deviceName string diff --git a/vsphere/resource_vsphere_virtual_machine_test.go b/vsphere/resource_vsphere_virtual_machine_test.go index 7428a69cc..ed137c8b6 100644 --- a/vsphere/resource_vsphere_virtual_machine_test.go +++ b/vsphere/resource_vsphere_virtual_machine_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "github.com/vmware/govmomi" "github.com/vmware/govmomi/vim25/types" ) @@ -313,6 +314,52 @@ func TestAccResourceVSphereVirtualMachine(t *testing.T) { }, }, }, + { + "windows template, customization events and proper IP", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereVirtualMachinePreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereVirtualMachineCheckExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereVirtualMachineConfigWindows(), + Check: resource.ComposeTestCheckFunc( + copyStatePtr(&state), + testAccResourceVSphereVirtualMachineCheckExists(true), + testAccResourceVSphereVirtualMachineCheckCustomizationSucceeded(), + testAccResourceVSphereVirtualMachineCheckNet( + os.Getenv("VSPHERE_IPV4_ADDRESS"), + os.Getenv("VSPHERE_IPV4_PREFIX"), + os.Getenv("VSPHERE_IPV4_GATEWAY"), + ), + ), + }, + }, + }, + }, + { + "dhcp only, don't wait for guest net", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereVirtualMachinePreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereVirtualMachineCheckExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereVirtualMachineConfigDHCPNoWait(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereVirtualMachineCheckExists(true), + testAccResourceVSphereVirtualMachineCheckCustomizationSucceeded(), + ), + }, + }, + }, + }, } for _, tc := range testAccResourceVSphereVirtualMachineCases { @@ -354,6 +401,9 @@ func testAccResourceVSphereVirtualMachinePreCheck(t *testing.T) { if os.Getenv("VSPHERE_TEMPLATE") == "" { t.Skip("set VSPHERE_TEMPLATE to run vsphere_virtual_machine acceptance tests") } + if os.Getenv("VSPHERE_TEMPLATE_WINDOWS") == "" { + t.Skip("set VSPHERE_TEMPLATE_WINDOWS to run vsphere_virtual_machine acceptance tests") + } } func testAccResourceVSphereVirtualMachineCheckExists(expected bool) resource.TestCheckFunc { @@ -620,6 +670,26 @@ func testAccResourceVSphereVirtualMachineCheckAnnotation() resource.TestCheckFun } } +// testAccResourceVSphereVirtualMachineCheckCustomizationSucceeded is a check +// to ensure that events have been received for customization success on a VM. +func testAccResourceVSphereVirtualMachineCheckCustomizationSucceeded() resource.TestCheckFunc { + return func(s *terraform.State) error { + vm, err := testGetVirtualMachine(s, "vm") + if err != nil { + return err + } + client := testAccProvider.Meta().(*govmomi.Client) + actual, err := selectEventsForReference(client, vm.Reference(), []string{eventTypeCustomizationSucceeded}) + if err != nil { + return err + } + if len(actual) < 1 { + return errors.New("customization success event was not received") + } + return nil + } +} + func testAccResourceVSphereVirtualMachineConfigBasic() string { return fmt.Sprintf(` variable "datacenter" { @@ -1500,3 +1570,151 @@ resource "vsphere_virtual_machine" "vm" { testAccResourceVSphereVirtualMachineAnnotation, ) } + +func testAccResourceVSphereVirtualMachineConfigWindows() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "cluster" { + default = "%s" +} + +variable "resource_pool" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +variable "ipv4_address" { + default = "%s" +} + +variable "ipv4_prefix" { + default = "%s" +} + +variable "ipv4_gateway" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "template" { + default = "%s" +} + +variable "linked_clone" { + default = "%s" +} + +resource "vsphere_virtual_machine" "vm" { + name = "terraform-test" + datacenter = "${var.datacenter}" + cluster = "${var.cluster}" + resource_pool = "${var.resource_pool}" + + vcpu = 4 + memory = 4096 + + network_interface { + label = "${var.network_label}" + ipv4_address = "${var.ipv4_address}" + ipv4_prefix_length = "${var.ipv4_prefix}" + ipv4_gateway = "${var.ipv4_gateway}" + } + + disk { + datastore = "${var.datastore}" + template = "${var.template}" + iops = 500 + } + + windows_opt_config { + admin_password = "VMw4re" + } + + linked_clone = "${var.linked_clone != "" ? "true" : "false" }" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_CLUSTER"), + os.Getenv("VSPHERE_RESOURCE_POOL"), + os.Getenv("VSPHERE_NETWORK_LABEL"), + os.Getenv("VSPHERE_IPV4_ADDRESS"), + os.Getenv("VSPHERE_IPV4_PREFIX"), + os.Getenv("VSPHERE_IPV4_GATEWAY"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_TEMPLATE_WINDOWS"), + os.Getenv("VSPHERE_USE_LINKED_CLONE"), + ) +} + +func testAccResourceVSphereVirtualMachineConfigDHCPNoWait() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "cluster" { + default = "%s" +} + +variable "resource_pool" { + default = "%s" +} + +variable "network_label" { + default = "%s" +} + +variable "datastore" { + default = "%s" +} + +variable "template" { + default = "%s" +} + +variable "linked_clone" { + default = "%s" +} + +resource "vsphere_virtual_machine" "vm" { + name = "terraform-test" + datacenter = "${var.datacenter}" + cluster = "${var.cluster}" + resource_pool = "${var.resource_pool}" + + vcpu = 2 + memory = 1024 + + network_interface { + label = "${var.network_label}" + } + + wait_for_guest_net = false + + disk { + datastore = "${var.datastore}" + template = "${var.template}" + iops = 500 + } + + linked_clone = "${var.linked_clone != "" ? "true" : "false" }" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_CLUSTER"), + os.Getenv("VSPHERE_RESOURCE_POOL"), + os.Getenv("VSPHERE_NETWORK_LABEL"), + os.Getenv("VSPHERE_DATASTORE"), + os.Getenv("VSPHERE_TEMPLATE"), + os.Getenv("VSPHERE_USE_LINKED_CLONE"), + ) +} diff --git a/vsphere/virtual_machine_helper.go b/vsphere/virtual_machine_helper.go index d29daa2fc..ef6682c2d 100644 --- a/vsphere/virtual_machine_helper.go +++ b/vsphere/virtual_machine_helper.go @@ -2,12 +2,16 @@ package vsphere import ( "context" + "errors" "fmt" + "net" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" ) // virtualMachineFromUUID locates a virtualMachine by its UUID. @@ -43,6 +47,28 @@ func virtualMachineFromUUID(client *govmomi.Client, uuid string) (*object.Virtua return vm.(*object.VirtualMachine), nil } +// virtualMachineFromManagedObjectID locates a virtualMachine by its managed +// object reference ID. +func virtualMachineFromManagedObjectID(client *govmomi.Client, id string) (*object.VirtualMachine, error) { + finder := find.NewFinder(client.Client, false) + + ref := types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: id, + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + vm, err := finder.ObjectReference(ctx, ref) + if err != nil { + return nil, err + } + // Should be safe to return here. If our reference returned here and is not a + // VM, then we have bigger problems and to be honest we should be panicking + // anyway. + return vm.(*object.VirtualMachine), nil +} + // virtualMachineProperties is a convenience method that wraps fetching the // VirtualMachine MO from its higher-level object. func virtualMachineProperties(vm *object.VirtualMachine) (*mo.VirtualMachine, error) { @@ -54,3 +80,68 @@ func virtualMachineProperties(vm *object.VirtualMachine) (*mo.VirtualMachine, er } return &props, nil } + +// waitForGuestVMNet waits for a virtual machine to have routeable network +// access. This is denoted as a gateway, and at least one IP address that can +// reach that gateway. This function supports both IPv4 and IPv6, and returns +// the moment either stack is routeable - it doesn't wait for both. +func waitForGuestVMNet(client *govmomi.Client, vm *object.VirtualMachine) error { + var v4gw, v6gw net.IP + + p := client.PropertyCollector() + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + + err := property.Wait(ctx, p, vm.Reference(), []string{"guest.net", "guest.ipStack"}, func(pc []types.PropertyChange) bool { + for _, c := range pc { + if c.Op != types.PropertyChangeOpAssign { + continue + } + + switch v := c.Val.(type) { + case types.ArrayOfGuestStackInfo: + for _, s := range v.GuestStackInfo { + if s.IpRouteConfig != nil { + for _, r := range s.IpRouteConfig.IpRoute { + switch r.Network { + case "0.0.0.0": + v4gw = net.ParseIP(r.Gateway.IpAddress) + case "::": + v6gw = net.ParseIP(r.Gateway.IpAddress) + } + } + } + } + case types.ArrayOfGuestNicInfo: + for _, n := range v.GuestNicInfo { + if n.IpConfig != nil { + for _, addr := range n.IpConfig.IpAddress { + ip := net.ParseIP(addr.IpAddress) + var mask net.IPMask + if ip.To4() != nil { + mask = net.CIDRMask(int(addr.PrefixLength), 32) + } else { + mask = net.CIDRMask(int(addr.PrefixLength), 128) + } + if ip.Mask(mask).Equal(v4gw.Mask(mask)) || ip.Mask(mask).Equal(v6gw.Mask(mask)) { + return true + } + } + } + } + } + } + + return false + }) + + if err != nil { + // Provide a friendly error message if we timed out waiting for a routeable IP. + if ctx.Err() == context.DeadlineExceeded { + return errors.New("timeout waiting for a routeable interface") + } + return err + } + + return nil +} diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 4e19db83a..3d14fe8cd 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -61,41 +61,79 @@ resource "vsphere_virtual_machine" "lb" { The following arguments are supported: -* `name` - (Required) The virtual machine name (cannot contain underscores and must be less than 15 characters) +* `name` - (Required) The virtual machine name (cannot contain underscores and + must be less than 15 characters) * `folder` - (Optional) The folder to group the VM in. -* `vcpu` - (Required) The number of virtual CPUs to allocate to the virtual machine -* `memory` - (Required) The amount of RAM (in MB) to allocate to the virtual machine -* `hostname` - (Optional) The virtual machine hostname used during the OS customization. Defaults to the `name` attribute. -* `memory_reservation` - (Optional) The amount of RAM (in MB) to reserve physical memory resource; defaults to 0 (means not to reserve) -* `datacenter` - (Optional) The name of a Datacenter in which to launch the virtual machine -* `cluster` - (Optional) Name of a Cluster in which to launch the virtual machine -* `resource_pool` (Optional) The name of a Resource Pool in which to launch the virtual machine. Requires full path (see cluster example). -* `gateway` - __Deprecated, please use `network_interface.ipv4_gateway` instead__. -* `domain` - (Optional) A FQDN for the virtual machine; defaults to "vsphere.local" -* `time_zone` - (Optional) The [Linux](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) or [Windows](https://msdn.microsoft.com/en-us/library/ms912391.aspx) time zone to set on the virtual machine. Defaults to "Etc/UTC" -* `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual network adapter -* `dns_servers` - (Optional) List of DNS servers for the virtual network adapter; defaults to 8.8.8.8, 8.8.4.4 -* `network_interface` - (Required) Configures virtual network interfaces; see [Network Interfaces](#network-interfaces) below for details. -* `disk` - (Required) Configures virtual disks; see [Disks](#disks) below for details -* `detach_unknown_disks_on_delete` - (Optional) will detach disks not managed by this resource on delete (avoids deletion of disks attached after resource creation outside of Terraform scope). -* `cdrom` - (Optional) Configures a CDROM device and mounts an image as its media; see [CDROM](#cdrom) below for more details. -* `windows_opt_config` - (Optional) Extra options for clones of Windows machines. -* `linked_clone` - (Optional) Specifies if the new machine is a [linked clone](https://www.vmware.com/support/ws5/doc/ws_clone_overview.html#wp1036396) of another machine or not. -* `enable_disk_uuid` - (Optional) This option causes the vm to mount disks by uuid on the guest OS. -* `custom_configuration_parameters` - (Optional) Map of values that is set as virtual machine custom configurations. -* `skip_customization` - (Optional) skip virtual machine customization (useful if OS is not in the guest OS support matrix of VMware like "other3xLinux64Guest"). +* `vcpu` - (Required) The number of virtual CPUs to allocate to the virtual + machine +* `memory` - (Required) The amount of RAM (in MB) to allocate to the virtual + machine +* `hostname` - (Optional) The virtual machine hostname used during the OS + customization. Defaults to the `name` attribute. +* `memory_reservation` - (Optional) The amount of RAM (in MB) to reserve + physical memory resource; defaults to 0 (means not to reserve) +* `datacenter` - (Optional) The name of a Datacenter in which to launch the + virtual machine +* `cluster` - (Optional) Name of a Cluster in which to launch the virtual + machine +* `resource_pool` (Optional) The name of a Resource Pool in which to launch the + virtual machine. Requires full path (see cluster example). +* `gateway` - __Deprecated, please use `network_interface.ipv4_gateway` + instead__. +* `domain` - (Optional) A FQDN for the virtual machine; defaults to + "vsphere.local" +* `time_zone` - (Optional) The + [Linux](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) + or [Windows](https://msdn.microsoft.com/en-us/library/ms912391.aspx) time + zone to set on the virtual machine. Defaults to "Etc/UTC" +* `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual + network adapter +* `dns_servers` - (Optional) List of DNS servers for the virtual network + adapter; defaults to 8.8.8.8, 8.8.4.4 +* `network_interface` - (Required) Configures virtual network interfaces; see + [Network Interfaces](#network-interfaces) below for details. +* `disk` - (Required) Configures virtual disks; see [Disks](#disks) below for + details +* `detach_unknown_disks_on_delete` - (Optional) will detach disks not managed + by this resource on delete (avoids deletion of disks attached after resource + creation outside of Terraform scope). +* `cdrom` - (Optional) Configures a CDROM device and mounts an image as its + media; see [CDROM](#cdrom) below for more details. +* `windows_opt_config` - (Optional) Extra options for clones of Windows + machines. +* `linked_clone` - (Optional) Specifies if the new machine is a [linked + clone](https://www.vmware.com/support/ws5/doc/ws_clone_overview.html#wp1036396) + of another machine or not. +* `enable_disk_uuid` - (Optional) This option causes the vm to mount disks by + uuid on the guest OS. +* `custom_configuration_parameters` - (Optional) Map of values that is set as + virtual machine custom configurations. +* `skip_customization` - (Optional) Skip virtual machine customization (useful + if OS is not in the guest OS support matrix of VMware like + "other3xLinux64Guest"). +* `wait_for_guest_net` - (Optional) Whether or not to wait for a VM to have + routeable network access. Should be set to `false` if none of the defined + `network_interface`s has a gateway assigned, or if all interfaces have been + left unconfigured. Default: `true`. * `annotation` - (Optional) Edit the annotation notes field The `network_interface` block supports: * `label` - (Required) Label to assign to this network interface -* `ipv4_address` - (Optional) Static IPv4 to assign to this network interface. Interface will use DHCP if this is left blank. -* `ipv4_prefix_length` - (Optional) prefix length to use when statically assigning an IPv4 address. +* `ipv4_address` - (Optional) Static IPv4 to assign to this network interface. + Interface will use DHCP if this is left blank. +* `ipv4_prefix_length` - (Optional) prefix length to use when statically + assigning an IPv4 address. * `ipv4_gateway` - (Optional) IPv4 gateway IP address to use. -* `ipv6_address` - (Optional) Static IPv6 to assign to this network interface. Interface will use DHCPv6 if this is left blank. -* `ipv6_prefix_length` - (Optional) prefix length to use when statically assigning an IPv6. +* `ipv6_address` - (Optional) Static IPv6 to assign to this network interface. + Interface will use DHCPv6 if this is left blank. +* `ipv6_prefix_length` - (Optional) prefix length to use when statically + assigning an IPv6. * `ipv6_gateway` - (Optional) IPv6 gateway IP address to use. -* `mac_address` - (Optional) Manual MAC address to assign to this network interface. Will be generated by VMware if not set. ([VMware KB: Setting a static MAC address for a virtual NIC (219)](https://kb.vmware.com/selfservice/microsites/search.do?cmd=displayKC&externalId=219)) +* `mac_address` - (Optional) Manual MAC address to assign to this network + interface. Will be generated by VMware if not set. ([VMware KB: Setting a + static MAC address for a virtual NIC + (219)](https://kb.vmware.com/selfservice/microsites/search.do?cmd=displayKC&externalId=219)) The following arguments are maintained for backwards compatibility and may be removed in a future version: @@ -105,9 +143,14 @@ removed in a future version: The `windows_opt_config` block supports: -* `product_key` - (Optional) Serial number for new installation of Windows. This serial number is ignored if the original guest operating system was installed using a volume-licensed CD. -* `admin_password` - (Optional) The password for the new `administrator` account. Omit for passwordless admin (using `""` does not work). -* `domain` - (Optional) Domain that the new machine will be placed into. If `domain`, `domain_user`, and `domain_user_password` are not all set, all three will be ignored. +* `product_key` - (Optional) Serial number for new installation of Windows. + This serial number is ignored if the original guest operating system was + installed using a volume-licensed CD. +* `admin_password` - (Optional) The password for the new `administrator` + account. Omit for passwordless admin (using `""` does not work). +* `domain` - (Optional) Domain that the new machine will be placed into. If + `domain`, `domain_user`, and `domain_user_password` are not all set, all + three will be ignored. * `domain_user` - (Optional) User that is a member of the specified domain. * `domain_user_password` - (Optional) Password for domain user, in plain text. @@ -116,15 +159,22 @@ The `windows_opt_config` block supports: The `disk` block supports: -* `template` - (Required if size and bootable_vmdk_path not provided) Template for this disk. +* `template` - (Required if size and bootable_vmdk_path not provided) Template + for this disk. * `datastore` - (Optional) Datastore for this disk -* `size` - (Required if template and bootable_vmdks_path not provided) Size of this disk (in GB). -* `name` - (Required if size is provided when creating a new disk) This "name" is used for the disk file name in vSphere, when the new disk is created. +* `size` - (Required if template and bootable_vmdks_path not provided) Size of + this disk (in GB). +* `name` - (Required if size is provided when creating a new disk) This "name" + is used for the disk file name in vSphere, when the new disk is created. * `iops` - (Optional) Number of virtual iops to allocate for this disk. -* `type` - (Optional) 'eager_zeroed' (the default), 'lazy', or 'thin' are supported options. -* `vmdk` - (Required if template and size not provided) Path to a vmdk in a vSphere datastore. -* `bootable` - (Optional) Set to 'true' if a vmdk was given and it should attempt to boot after creation. -* `controller_type` - (Optional) Controller type to attach the disk to. 'scsi' (the default), or 'ide' are supported options. +* `type` - (Optional) 'eager_zeroed' (the default), 'lazy', or 'thin' are + supported options. +* `vmdk` - (Required if template and size not provided) Path to a vmdk in a + vSphere datastore. +* `bootable` - (Optional) Set to 'true' if a vmdk was given and it should + attempt to boot after creation. +* `controller_type` - (Optional) Controller type to attach the disk to. 'scsi' + (the default), or 'ide' are supported options. * `keep_on_remove` - (Optional) Set to 'true' to not delete a disk on removal. @@ -132,7 +182,8 @@ The `disk` block supports: The `cdrom` block supports: -* `datastore` - (Required) The name of the datastore where the disk image is stored. +* `datastore` - (Required) The name of the datastore where the disk image is + stored. * `path` - (Required) The absolute path to the image within the datastore. ## Attributes Reference @@ -150,7 +201,8 @@ The following attributes are exported: * `network_interface/ipv4_address` - See Argument Reference above. * `network_interface/ipv4_prefix_length` - See Argument Reference above. * `network_interface/ipv6_address` - Assigned static IPv6 address. -* `network_interface/ipv6_prefix_length` - Prefix length of assigned static IPv6 address. +* `network_interface/ipv6_prefix_length` - Prefix length of assigned static + IPv6 address. * `power_state` - The power state of the virtual machine. Can be one of `poweredOff`, `poweredOn`, or `suspended`.