github.com/memsql/terraform@v0.7.0-rc2.0.20160706152241-21e2173e0a32/builtin/providers/vsphere/resource_vsphere_virtual_machine.go (about)

     1  package vsphere
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"github.com/vmware/govmomi"
    12  	"github.com/vmware/govmomi/find"
    13  	"github.com/vmware/govmomi/object"
    14  	"github.com/vmware/govmomi/property"
    15  	"github.com/vmware/govmomi/vim25/mo"
    16  	"github.com/vmware/govmomi/vim25/types"
    17  	"golang.org/x/net/context"
    18  )
    19  
    20  var DefaultDNSSuffixes = []string{
    21  	"vsphere.local",
    22  }
    23  
    24  var DefaultDNSServers = []string{
    25  	"8.8.8.8",
    26  	"8.8.4.4",
    27  }
    28  
    29  type networkInterface struct {
    30  	deviceName       string
    31  	label            string
    32  	ipv4Address      string
    33  	ipv4PrefixLength int
    34  	ipv4Gateway      string
    35  	ipv6Address      string
    36  	ipv6PrefixLength int
    37  	ipv6Gateway      string
    38  	adapterType      string // TODO: Make "adapter_type" argument
    39  	macAddress       string
    40  }
    41  
    42  type hardDisk struct {
    43  	name       string
    44  	size       int64
    45  	iops       int64
    46  	initType   string
    47  	vmdkPath   string
    48  	controller string
    49  	bootable   bool
    50  }
    51  
    52  //Additional options Vsphere can use clones of windows machines
    53  type windowsOptConfig struct {
    54  	productKey         string
    55  	adminPassword      string
    56  	domainUser         string
    57  	domain             string
    58  	domainUserPassword string
    59  }
    60  
    61  type cdrom struct {
    62  	datastore string
    63  	path      string
    64  }
    65  
    66  type memoryAllocation struct {
    67  	reservation int64
    68  }
    69  
    70  type virtualMachine struct {
    71  	name                  string
    72  	folder                string
    73  	datacenter            string
    74  	cluster               string
    75  	resourcePool          string
    76  	datastore             string
    77  	vcpu                  int32
    78  	memoryMb              int64
    79  	memoryAllocation      memoryAllocation
    80  	template              string
    81  	networkInterfaces     []networkInterface
    82  	hardDisks             []hardDisk
    83  	cdroms                []cdrom
    84  	domain                string
    85  	timeZone              string
    86  	dnsSuffixes           []string
    87  	dnsServers            []string
    88  	hasBootableVmdk       bool
    89  	linkedClone           bool
    90  	skipCustomization     bool
    91  	enableDiskUUID        bool
    92  	windowsOptionalConfig windowsOptConfig
    93  	customConfigurations  map[string](types.AnyType)
    94  }
    95  
    96  func (v virtualMachine) Path() string {
    97  	return vmPath(v.folder, v.name)
    98  }
    99  
   100  func vmPath(folder string, name string) string {
   101  	var path string
   102  	if len(folder) > 0 {
   103  		path += folder + "/"
   104  	}
   105  	return path + name
   106  }
   107  
   108  func resourceVSphereVirtualMachine() *schema.Resource {
   109  	return &schema.Resource{
   110  		Create: resourceVSphereVirtualMachineCreate,
   111  		Read:   resourceVSphereVirtualMachineRead,
   112  		Update: resourceVSphereVirtualMachineUpdate,
   113  		Delete: resourceVSphereVirtualMachineDelete,
   114  
   115  		SchemaVersion: 1,
   116  		MigrateState:  resourceVSphereVirtualMachineMigrateState,
   117  
   118  		Schema: map[string]*schema.Schema{
   119  			"name": &schema.Schema{
   120  				Type:     schema.TypeString,
   121  				Required: true,
   122  				ForceNew: true,
   123  			},
   124  
   125  			"folder": &schema.Schema{
   126  				Type:     schema.TypeString,
   127  				Optional: true,
   128  				ForceNew: true,
   129  			},
   130  
   131  			"vcpu": &schema.Schema{
   132  				Type:     schema.TypeInt,
   133  				Required: true,
   134  			},
   135  
   136  			"memory": &schema.Schema{
   137  				Type:     schema.TypeInt,
   138  				Required: true,
   139  			},
   140  
   141  			"memory_reservation": &schema.Schema{
   142  				Type:     schema.TypeInt,
   143  				Optional: true,
   144  				Default:  0,
   145  				ForceNew: true,
   146  			},
   147  
   148  			"datacenter": &schema.Schema{
   149  				Type:     schema.TypeString,
   150  				Optional: true,
   151  				ForceNew: true,
   152  			},
   153  
   154  			"cluster": &schema.Schema{
   155  				Type:     schema.TypeString,
   156  				Optional: true,
   157  				ForceNew: true,
   158  			},
   159  
   160  			"resource_pool": &schema.Schema{
   161  				Type:     schema.TypeString,
   162  				Optional: true,
   163  				ForceNew: true,
   164  			},
   165  
   166  			"linked_clone": &schema.Schema{
   167  				Type:     schema.TypeBool,
   168  				Optional: true,
   169  				Default:  false,
   170  				ForceNew: true,
   171  			},
   172  			"gateway": &schema.Schema{
   173  				Type:       schema.TypeString,
   174  				Optional:   true,
   175  				ForceNew:   true,
   176  				Deprecated: "Please use network_interface.ipv4_gateway",
   177  			},
   178  
   179  			"domain": &schema.Schema{
   180  				Type:     schema.TypeString,
   181  				Optional: true,
   182  				ForceNew: true,
   183  				Default:  "vsphere.local",
   184  			},
   185  
   186  			"time_zone": &schema.Schema{
   187  				Type:     schema.TypeString,
   188  				Optional: true,
   189  				ForceNew: true,
   190  				Default:  "Etc/UTC",
   191  			},
   192  
   193  			"dns_suffixes": &schema.Schema{
   194  				Type:     schema.TypeList,
   195  				Optional: true,
   196  				Elem:     &schema.Schema{Type: schema.TypeString},
   197  				ForceNew: true,
   198  			},
   199  
   200  			"dns_servers": &schema.Schema{
   201  				Type:     schema.TypeList,
   202  				Optional: true,
   203  				Elem:     &schema.Schema{Type: schema.TypeString},
   204  				ForceNew: true,
   205  			},
   206  
   207  			"skip_customization": &schema.Schema{
   208  				Type:     schema.TypeBool,
   209  				Optional: true,
   210  				ForceNew: true,
   211  				Default:  false,
   212  			},
   213  
   214  			"enable_disk_uuid": &schema.Schema{
   215  				Type:     schema.TypeBool,
   216  				Optional: true,
   217  				ForceNew: true,
   218  				Default:  false,
   219  			},
   220  
   221  			"uuid": &schema.Schema{
   222  				Type:     schema.TypeString,
   223  				Computed: true,
   224  			},
   225  
   226  			"custom_configuration_parameters": &schema.Schema{
   227  				Type:     schema.TypeMap,
   228  				Optional: true,
   229  				ForceNew: true,
   230  			},
   231  
   232  			"windows_opt_config": &schema.Schema{
   233  				Type:     schema.TypeList,
   234  				Optional: true,
   235  				ForceNew: true,
   236  				Elem: &schema.Resource{
   237  					Schema: map[string]*schema.Schema{
   238  						"product_key": &schema.Schema{
   239  							Type:     schema.TypeString,
   240  							Optional: true,
   241  							ForceNew: true,
   242  						},
   243  
   244  						"admin_password": &schema.Schema{
   245  							Type:     schema.TypeString,
   246  							Optional: true,
   247  							ForceNew: true,
   248  						},
   249  
   250  						"domain_user": &schema.Schema{
   251  							Type:     schema.TypeString,
   252  							Optional: true,
   253  							ForceNew: true,
   254  						},
   255  
   256  						"domain": &schema.Schema{
   257  							Type:     schema.TypeString,
   258  							Optional: true,
   259  							ForceNew: true,
   260  						},
   261  
   262  						"domain_user_password": &schema.Schema{
   263  							Type:     schema.TypeString,
   264  							Optional: true,
   265  							ForceNew: true,
   266  						},
   267  					},
   268  				},
   269  			},
   270  
   271  			"network_interface": &schema.Schema{
   272  				Type:     schema.TypeList,
   273  				Required: true,
   274  				ForceNew: true,
   275  				Elem: &schema.Resource{
   276  					Schema: map[string]*schema.Schema{
   277  						"label": &schema.Schema{
   278  							Type:     schema.TypeString,
   279  							Required: true,
   280  							ForceNew: true,
   281  						},
   282  
   283  						"ip_address": &schema.Schema{
   284  							Type:       schema.TypeString,
   285  							Optional:   true,
   286  							Computed:   true,
   287  							Deprecated: "Please use ipv4_address",
   288  						},
   289  
   290  						"subnet_mask": &schema.Schema{
   291  							Type:       schema.TypeString,
   292  							Optional:   true,
   293  							Computed:   true,
   294  							Deprecated: "Please use ipv4_prefix_length",
   295  						},
   296  
   297  						"ipv4_address": &schema.Schema{
   298  							Type:     schema.TypeString,
   299  							Optional: true,
   300  							Computed: true,
   301  						},
   302  
   303  						"ipv4_prefix_length": &schema.Schema{
   304  							Type:     schema.TypeInt,
   305  							Optional: true,
   306  							Computed: true,
   307  						},
   308  
   309  						"ipv4_gateway": &schema.Schema{
   310  							Type:     schema.TypeString,
   311  							Optional: true,
   312  							Computed: true,
   313  						},
   314  
   315  						"ipv6_address": &schema.Schema{
   316  							Type:     schema.TypeString,
   317  							Optional: true,
   318  							Computed: true,
   319  						},
   320  
   321  						"ipv6_prefix_length": &schema.Schema{
   322  							Type:     schema.TypeInt,
   323  							Optional: true,
   324  							Computed: true,
   325  						},
   326  
   327  						"ipv6_gateway": &schema.Schema{
   328  							Type:     schema.TypeString,
   329  							Optional: true,
   330  							Computed: true,
   331  						},
   332  
   333  						"adapter_type": &schema.Schema{
   334  							Type:     schema.TypeString,
   335  							Optional: true,
   336  							ForceNew: true,
   337  						},
   338  
   339  						"mac_address": &schema.Schema{
   340  							Type:     schema.TypeString,
   341  							Optional: true,
   342  							Computed: true,
   343  						},
   344  					},
   345  				},
   346  			},
   347  
   348  			"disk": &schema.Schema{
   349  				Type:     schema.TypeSet,
   350  				Required: true,
   351  				Elem: &schema.Resource{
   352  					Schema: map[string]*schema.Schema{
   353  						"uuid": &schema.Schema{
   354  							Type:     schema.TypeString,
   355  							Computed: true,
   356  						},
   357  
   358  						"key": &schema.Schema{
   359  							Type:     schema.TypeInt,
   360  							Computed: true,
   361  						},
   362  
   363  						"template": &schema.Schema{
   364  							Type:     schema.TypeString,
   365  							Optional: true,
   366  						},
   367  
   368  						"type": &schema.Schema{
   369  							Type:     schema.TypeString,
   370  							Optional: true,
   371  							Default:  "eager_zeroed",
   372  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   373  								value := v.(string)
   374  								if value != "thin" && value != "eager_zeroed" {
   375  									errors = append(errors, fmt.Errorf(
   376  										"only 'thin' and 'eager_zeroed' are supported values for 'type'"))
   377  								}
   378  								return
   379  							},
   380  						},
   381  
   382  						"datastore": &schema.Schema{
   383  							Type:     schema.TypeString,
   384  							Optional: true,
   385  						},
   386  
   387  						"size": &schema.Schema{
   388  							Type:     schema.TypeInt,
   389  							Optional: true,
   390  						},
   391  
   392  						"name": &schema.Schema{
   393  							Type:     schema.TypeString,
   394  							Optional: true,
   395  						},
   396  
   397  						"iops": &schema.Schema{
   398  							Type:     schema.TypeInt,
   399  							Optional: true,
   400  						},
   401  
   402  						"vmdk": &schema.Schema{
   403  							// TODO: Add ValidateFunc to confirm path exists
   404  							Type:     schema.TypeString,
   405  							Optional: true,
   406  						},
   407  
   408  						"bootable": &schema.Schema{
   409  							Type:     schema.TypeBool,
   410  							Optional: true,
   411  						},
   412  
   413  						"keep_on_remove": &schema.Schema{
   414  							Type:     schema.TypeBool,
   415  							Optional: true,
   416  						},
   417  
   418  						"controller_type": &schema.Schema{
   419  							Type:     schema.TypeString,
   420  							Optional: true,
   421  							Default:  "scsi",
   422  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   423  								value := v.(string)
   424  								if value != "scsi" && value != "ide" {
   425  									errors = append(errors, fmt.Errorf(
   426  										"only 'scsi' and 'ide' are supported values for 'controller_type'"))
   427  								}
   428  								return
   429  							},
   430  						},
   431  					},
   432  				},
   433  			},
   434  
   435  			"cdrom": &schema.Schema{
   436  				Type:     schema.TypeList,
   437  				Optional: true,
   438  				ForceNew: true,
   439  				Elem: &schema.Resource{
   440  					Schema: map[string]*schema.Schema{
   441  						"datastore": &schema.Schema{
   442  							Type:     schema.TypeString,
   443  							Required: true,
   444  							ForceNew: true,
   445  						},
   446  
   447  						"path": &schema.Schema{
   448  							Type:     schema.TypeString,
   449  							Required: true,
   450  							ForceNew: true,
   451  						},
   452  					},
   453  				},
   454  			},
   455  		},
   456  	}
   457  }
   458  
   459  func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{}) error {
   460  	// flag if changes have to be applied
   461  	hasChanges := false
   462  	// flag if changes have to be done when powered off
   463  	rebootRequired := false
   464  
   465  	// make config spec
   466  	configSpec := types.VirtualMachineConfigSpec{}
   467  
   468  	if d.HasChange("vcpu") {
   469  		configSpec.NumCPUs = int32(d.Get("vcpu").(int))
   470  		hasChanges = true
   471  		rebootRequired = true
   472  	}
   473  
   474  	if d.HasChange("memory") {
   475  		configSpec.MemoryMB = int64(d.Get("memory").(int))
   476  		hasChanges = true
   477  		rebootRequired = true
   478  	}
   479  
   480  	client := meta.(*govmomi.Client)
   481  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   482  	if err != nil {
   483  		return err
   484  	}
   485  	finder := find.NewFinder(client.Client, true)
   486  	finder = finder.SetDatacenter(dc)
   487  
   488  	vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string)))
   489  	if err != nil {
   490  		return err
   491  	}
   492  
   493  	if d.HasChange("disk") {
   494  		hasChanges = true
   495  		oldDisks, newDisks := d.GetChange("disk")
   496  		oldDiskSet := oldDisks.(*schema.Set)
   497  		newDiskSet := newDisks.(*schema.Set)
   498  
   499  		addedDisks := newDiskSet.Difference(oldDiskSet)
   500  		removedDisks := oldDiskSet.Difference(newDiskSet)
   501  
   502  		// Removed disks
   503  		for _, diskRaw := range removedDisks.List() {
   504  			if disk, ok := diskRaw.(map[string]interface{}); ok {
   505  				devices, err := vm.Device(context.TODO())
   506  				if err != nil {
   507  					return fmt.Errorf("[ERROR] Update Remove Disk - Could not get virtual device list: %v", err)
   508  				}
   509  				virtualDisk := devices.FindByKey(int32(disk["key"].(int)))
   510  
   511  				keep := false
   512  				if v, ok := d.GetOk("keep_on_remove"); ok {
   513  					keep = v.(bool)
   514  				}
   515  
   516  				err = vm.RemoveDevice(context.TODO(), keep, virtualDisk)
   517  				if err != nil {
   518  					return fmt.Errorf("[ERROR] Update Remove Disk - Error removing disk: %v", err)
   519  				}
   520  			}
   521  		}
   522  		// Added disks
   523  		for _, diskRaw := range addedDisks.List() {
   524  			if disk, ok := diskRaw.(map[string]interface{}); ok {
   525  
   526  				var datastore *object.Datastore
   527  				if disk["datastore"] == "" {
   528  					datastore, err = finder.DefaultDatastore(context.TODO())
   529  					if err != nil {
   530  						return fmt.Errorf("[ERROR] Update Remove Disk - Error finding datastore: %v", err)
   531  					}
   532  				} else {
   533  					datastore, err = finder.Datastore(context.TODO(), disk["datastore"].(string))
   534  					if err != nil {
   535  						log.Printf("[ERROR] Couldn't find datastore %v.  %s", disk["datastore"].(string), err)
   536  						return err
   537  					}
   538  				}
   539  
   540  				var size int64
   541  				if disk["size"] == 0 {
   542  					size = 0
   543  				} else {
   544  					size = int64(disk["size"].(int))
   545  				}
   546  				iops := int64(disk["iops"].(int))
   547  				controller_type := disk["controller_type"].(string)
   548  
   549  				var mo mo.VirtualMachine
   550  				vm.Properties(context.TODO(), vm.Reference(), []string{"summary", "config"}, &mo)
   551  
   552  				var diskPath string
   553  				switch {
   554  				case disk["vmdk"] != "":
   555  					diskPath = disk["vmdk"].(string)
   556  				case disk["name"] != "":
   557  					snapshotFullDir := mo.Config.Files.SnapshotDirectory
   558  					split := strings.Split(snapshotFullDir, " ")
   559  					if len(split) != 2 {
   560  						return fmt.Errorf("[ERROR] createVirtualMachine - failed to split snapshot directory: %v", snapshotFullDir)
   561  					}
   562  					vmWorkingPath := split[1]
   563  					diskPath = vmWorkingPath + disk["name"].(string)
   564  				default:
   565  					return fmt.Errorf("[ERROR] resourceVSphereVirtualMachineUpdate - Neither vmdk path nor vmdk name was given")
   566  				}
   567  
   568  				log.Printf("[INFO] Attaching disk: %v", diskPath)
   569  				err = addHardDisk(vm, size, iops, "thin", datastore, diskPath, controller_type)
   570  				if err != nil {
   571  					log.Printf("[ERROR] Add Hard Disk Failed: %v", err)
   572  					return err
   573  				}
   574  			}
   575  			if err != nil {
   576  				return err
   577  			}
   578  		}
   579  	}
   580  
   581  	// do nothing if there are no changes
   582  	if !hasChanges {
   583  		return nil
   584  	}
   585  
   586  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
   587  
   588  	if rebootRequired {
   589  		log.Printf("[INFO] Shutting down virtual machine: %s", d.Id())
   590  
   591  		task, err := vm.PowerOff(context.TODO())
   592  		if err != nil {
   593  			return err
   594  		}
   595  
   596  		err = task.Wait(context.TODO())
   597  		if err != nil {
   598  			return err
   599  		}
   600  	}
   601  
   602  	log.Printf("[INFO] Reconfiguring virtual machine: %s", d.Id())
   603  
   604  	task, err := vm.Reconfigure(context.TODO(), configSpec)
   605  	if err != nil {
   606  		log.Printf("[ERROR] %s", err)
   607  	}
   608  
   609  	err = task.Wait(context.TODO())
   610  	if err != nil {
   611  		log.Printf("[ERROR] %s", err)
   612  	}
   613  
   614  	if rebootRequired {
   615  		task, err = vm.PowerOn(context.TODO())
   616  		if err != nil {
   617  			return err
   618  		}
   619  
   620  		err = task.Wait(context.TODO())
   621  		if err != nil {
   622  			log.Printf("[ERROR] %s", err)
   623  		}
   624  	}
   625  
   626  	ip, err := vm.WaitForIP(context.TODO())
   627  	if err != nil {
   628  		return err
   629  	}
   630  	log.Printf("[DEBUG] ip address: %v", ip)
   631  
   632  	return resourceVSphereVirtualMachineRead(d, meta)
   633  }
   634  
   635  func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error {
   636  	client := meta.(*govmomi.Client)
   637  
   638  	vm := virtualMachine{
   639  		name:     d.Get("name").(string),
   640  		vcpu:     int32(d.Get("vcpu").(int)),
   641  		memoryMb: int64(d.Get("memory").(int)),
   642  		memoryAllocation: memoryAllocation{
   643  			reservation: int64(d.Get("memory_reservation").(int)),
   644  		},
   645  	}
   646  
   647  	if v, ok := d.GetOk("folder"); ok {
   648  		vm.folder = v.(string)
   649  	}
   650  
   651  	if v, ok := d.GetOk("datacenter"); ok {
   652  		vm.datacenter = v.(string)
   653  	}
   654  
   655  	if v, ok := d.GetOk("cluster"); ok {
   656  		vm.cluster = v.(string)
   657  	}
   658  
   659  	if v, ok := d.GetOk("resource_pool"); ok {
   660  		vm.resourcePool = v.(string)
   661  	}
   662  
   663  	if v, ok := d.GetOk("domain"); ok {
   664  		vm.domain = v.(string)
   665  	}
   666  
   667  	if v, ok := d.GetOk("time_zone"); ok {
   668  		vm.timeZone = v.(string)
   669  	}
   670  
   671  	if v, ok := d.GetOk("linked_clone"); ok {
   672  		vm.linkedClone = v.(bool)
   673  	}
   674  
   675  	if v, ok := d.GetOk("skip_customization"); ok {
   676  		vm.skipCustomization = v.(bool)
   677  	}
   678  
   679  	if v, ok := d.GetOk("enable_disk_uuid"); ok {
   680  		vm.enableDiskUUID = v.(bool)
   681  	}
   682  
   683  	if raw, ok := d.GetOk("dns_suffixes"); ok {
   684  		for _, v := range raw.([]interface{}) {
   685  			vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string))
   686  		}
   687  	} else {
   688  		vm.dnsSuffixes = DefaultDNSSuffixes
   689  	}
   690  
   691  	if raw, ok := d.GetOk("dns_servers"); ok {
   692  		for _, v := range raw.([]interface{}) {
   693  			vm.dnsServers = append(vm.dnsServers, v.(string))
   694  		}
   695  	} else {
   696  		vm.dnsServers = DefaultDNSServers
   697  	}
   698  
   699  	if vL, ok := d.GetOk("custom_configuration_parameters"); ok {
   700  		if custom_configs, ok := vL.(map[string]interface{}); ok {
   701  			custom := make(map[string]types.AnyType)
   702  			for k, v := range custom_configs {
   703  				custom[k] = v
   704  			}
   705  			vm.customConfigurations = custom
   706  			log.Printf("[DEBUG] custom_configuration_parameters init: %v", vm.customConfigurations)
   707  		}
   708  	}
   709  
   710  	if vL, ok := d.GetOk("network_interface"); ok {
   711  		networks := make([]networkInterface, len(vL.([]interface{})))
   712  		for i, v := range vL.([]interface{}) {
   713  			network := v.(map[string]interface{})
   714  			networks[i].label = network["label"].(string)
   715  			if v, ok := network["ip_address"].(string); ok && v != "" {
   716  				networks[i].ipv4Address = v
   717  			}
   718  			if v, ok := d.GetOk("gateway"); ok {
   719  				networks[i].ipv4Gateway = v.(string)
   720  			}
   721  			if v, ok := network["subnet_mask"].(string); ok && v != "" {
   722  				ip := net.ParseIP(v).To4()
   723  				if ip != nil {
   724  					mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3])
   725  					pl, _ := mask.Size()
   726  					networks[i].ipv4PrefixLength = pl
   727  				} else {
   728  					return fmt.Errorf("subnet_mask parameter is invalid.")
   729  				}
   730  			}
   731  			if v, ok := network["ipv4_address"].(string); ok && v != "" {
   732  				networks[i].ipv4Address = v
   733  			}
   734  			if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 {
   735  				networks[i].ipv4PrefixLength = v
   736  			}
   737  			if v, ok := network["ipv4_gateway"].(string); ok && v != "" {
   738  				networks[i].ipv4Gateway = v
   739  			}
   740  			if v, ok := network["ipv6_address"].(string); ok && v != "" {
   741  				networks[i].ipv6Address = v
   742  			}
   743  			if v, ok := network["ipv6_prefix_length"].(int); ok && v != 0 {
   744  				networks[i].ipv6PrefixLength = v
   745  			}
   746  			if v, ok := network["ipv6_gateway"].(string); ok && v != "" {
   747  				networks[i].ipv6Gateway = v
   748  			}
   749  			if v, ok := network["mac_address"].(string); ok && v != "" {
   750  				networks[i].macAddress = v
   751  			}
   752  		}
   753  		vm.networkInterfaces = networks
   754  		log.Printf("[DEBUG] network_interface init: %v", networks)
   755  	}
   756  
   757  	if vL, ok := d.GetOk("windows_opt_config"); ok {
   758  		var winOpt windowsOptConfig
   759  		custom_configs := (vL.([]interface{}))[0].(map[string]interface{})
   760  		if v, ok := custom_configs["admin_password"].(string); ok && v != "" {
   761  			winOpt.adminPassword = v
   762  		}
   763  		if v, ok := custom_configs["domain"].(string); ok && v != "" {
   764  			winOpt.domain = v
   765  		}
   766  		if v, ok := custom_configs["domain_user"].(string); ok && v != "" {
   767  			winOpt.domainUser = v
   768  		}
   769  		if v, ok := custom_configs["product_key"].(string); ok && v != "" {
   770  			winOpt.productKey = v
   771  		}
   772  		if v, ok := custom_configs["domain_user_password"].(string); ok && v != "" {
   773  			winOpt.domainUserPassword = v
   774  		}
   775  		vm.windowsOptionalConfig = winOpt
   776  		log.Printf("[DEBUG] windows config init: %v", winOpt)
   777  	}
   778  
   779  	if vL, ok := d.GetOk("disk"); ok {
   780  		if diskSet, ok := vL.(*schema.Set); ok {
   781  
   782  			disks := []hardDisk{}
   783  			hasBootableDisk := false
   784  			for _, value := range diskSet.List() {
   785  				disk := value.(map[string]interface{})
   786  				newDisk := hardDisk{}
   787  
   788  				if v, ok := disk["template"].(string); ok && v != "" {
   789  					if v, ok := disk["name"].(string); ok && v != "" {
   790  						return fmt.Errorf("Cannot specify name of a template")
   791  					}
   792  					vm.template = v
   793  					if hasBootableDisk {
   794  						return fmt.Errorf("[ERROR] Only one bootable disk or template may be given")
   795  					}
   796  					hasBootableDisk = true
   797  				}
   798  
   799  				if v, ok := disk["type"].(string); ok && v != "" {
   800  					newDisk.initType = v
   801  				}
   802  
   803  				if v, ok := disk["datastore"].(string); ok && v != "" {
   804  					vm.datastore = v
   805  				}
   806  
   807  				if v, ok := disk["size"].(int); ok && v != 0 {
   808  					if v, ok := disk["template"].(string); ok && v != "" {
   809  						return fmt.Errorf("Cannot specify size of a template")
   810  					}
   811  
   812  					if v, ok := disk["name"].(string); ok && v != "" {
   813  						newDisk.name = v
   814  					} else {
   815  						return fmt.Errorf("[ERROR] Disk name must be provided when creating a new disk")
   816  					}
   817  
   818  					newDisk.size = int64(v)
   819  				}
   820  
   821  				if v, ok := disk["iops"].(int); ok && v != 0 {
   822  					newDisk.iops = int64(v)
   823  				}
   824  
   825  				if v, ok := disk["controller_type"].(string); ok && v != "" {
   826  					newDisk.controller = v
   827  				}
   828  
   829  				if vVmdk, ok := disk["vmdk"].(string); ok && vVmdk != "" {
   830  					if v, ok := disk["template"].(string); ok && v != "" {
   831  						return fmt.Errorf("Cannot specify a vmdk for a template")
   832  					}
   833  					if v, ok := disk["size"].(string); ok && v != "" {
   834  						return fmt.Errorf("Cannot specify size of a vmdk")
   835  					}
   836  					if v, ok := disk["name"].(string); ok && v != "" {
   837  						return fmt.Errorf("Cannot specify name of a vmdk")
   838  					}
   839  					if vBootable, ok := disk["bootable"].(bool); ok {
   840  						hasBootableDisk = true
   841  						newDisk.bootable = vBootable
   842  						vm.hasBootableVmdk = vBootable
   843  					}
   844  					newDisk.vmdkPath = vVmdk
   845  				}
   846  				// Preserves order so bootable disk is first
   847  				if newDisk.bootable == true || disk["template"] != "" {
   848  					disks = append([]hardDisk{newDisk}, disks...)
   849  				} else {
   850  					disks = append(disks, newDisk)
   851  				}
   852  			}
   853  			vm.hardDisks = disks
   854  			log.Printf("[DEBUG] disk init: %v", disks)
   855  		}
   856  	}
   857  
   858  	if vL, ok := d.GetOk("cdrom"); ok {
   859  		cdroms := make([]cdrom, len(vL.([]interface{})))
   860  		for i, v := range vL.([]interface{}) {
   861  			c := v.(map[string]interface{})
   862  			if v, ok := c["datastore"].(string); ok && v != "" {
   863  				cdroms[i].datastore = v
   864  			} else {
   865  				return fmt.Errorf("Datastore argument must be specified when attaching a cdrom image.")
   866  			}
   867  			if v, ok := c["path"].(string); ok && v != "" {
   868  				cdroms[i].path = v
   869  			} else {
   870  				return fmt.Errorf("Path argument must be specified when attaching a cdrom image.")
   871  			}
   872  		}
   873  		vm.cdroms = cdroms
   874  		log.Printf("[DEBUG] cdrom init: %v", cdroms)
   875  	}
   876  
   877  	err := vm.setupVirtualMachine(client)
   878  	if err != nil {
   879  		return err
   880  	}
   881  
   882  	d.SetId(vm.Path())
   883  	log.Printf("[INFO] Created virtual machine: %s", d.Id())
   884  
   885  	return resourceVSphereVirtualMachineRead(d, meta)
   886  }
   887  
   888  func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error {
   889  	log.Printf("[DEBUG] virtual machine resource data: %#v", d)
   890  	client := meta.(*govmomi.Client)
   891  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   892  	if err != nil {
   893  		return err
   894  	}
   895  	finder := find.NewFinder(client.Client, true)
   896  	finder = finder.SetDatacenter(dc)
   897  
   898  	vm, err := finder.VirtualMachine(context.TODO(), d.Id())
   899  	if err != nil {
   900  		d.SetId("")
   901  		return nil
   902  	}
   903  
   904  	var mvm mo.VirtualMachine
   905  
   906  	// wait for interfaces to appear
   907  	_, err = vm.WaitForNetIP(context.TODO(), true)
   908  	if err != nil {
   909  		return err
   910  	}
   911  
   912  	collector := property.DefaultCollector(client.Client)
   913  	if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore", "config"}, &mvm); err != nil {
   914  		return err
   915  	}
   916  
   917  	log.Printf("[DEBUG] Datacenter - %#v", dc)
   918  	log.Printf("[DEBUG] mvm.Summary.Config - %#v", mvm.Summary.Config)
   919  	log.Printf("[DEBUG] mvm.Summary.Config - %#v", mvm.Config)
   920  	log.Printf("[DEBUG] mvm.Guest.Net - %#v", mvm.Guest.Net)
   921  
   922  	disks := make([]map[string]interface{}, 0)
   923  	templateDisk := make(map[string]interface{}, 1)
   924  	for _, device := range mvm.Config.Hardware.Device {
   925  		if vd, ok := device.(*types.VirtualDisk); ok {
   926  
   927  			virtualDevice := vd.GetVirtualDevice()
   928  
   929  			backingInfo := virtualDevice.Backing
   930  			var diskFullPath string
   931  			var diskUuid string
   932  			if v, ok := backingInfo.(*types.VirtualDiskFlatVer2BackingInfo); ok {
   933  				diskFullPath = v.FileName
   934  				diskUuid = v.Uuid
   935  			} else if v, ok := backingInfo.(*types.VirtualDiskSparseVer2BackingInfo); ok {
   936  				diskFullPath = v.FileName
   937  				diskUuid = v.Uuid
   938  			}
   939  			log.Printf("[DEBUG] resourceVSphereVirtualMachineRead - Analyzing disk: %v", diskFullPath)
   940  
   941  			// Separate datastore and path
   942  			diskFullPathSplit := strings.Split(diskFullPath, " ")
   943  			if len(diskFullPathSplit) != 2 {
   944  				return fmt.Errorf("[ERROR] Failed trying to parse disk path: %v", diskFullPath)
   945  			}
   946  			diskPath := diskFullPathSplit[1]
   947  			// Isolate filename
   948  			diskNameSplit := strings.Split(diskPath, "/")
   949  			diskName := diskNameSplit[len(diskNameSplit)-1]
   950  			// Remove possible extension
   951  			diskName = strings.Split(diskName, ".")[0]
   952  
   953  			if prevDisks, ok := d.GetOk("disk"); ok {
   954  				if prevDiskSet, ok := prevDisks.(*schema.Set); ok {
   955  					for _, v := range prevDiskSet.List() {
   956  						prevDisk := v.(map[string]interface{})
   957  
   958  						// We're guaranteed only one template disk.  Passing value directly through since templates should be immutable
   959  						if prevDisk["template"] != "" {
   960  							if len(templateDisk) == 0 {
   961  								templateDisk = prevDisk
   962  								disks = append(disks, templateDisk)
   963  								break
   964  							}
   965  						}
   966  
   967  						// It is enforced that prevDisk["name"] should only be set in the case
   968  						// of creating a new disk for the user.
   969  						// size case:  name was set by user, compare parsed filename from mo.filename (without path or .vmdk extension) with name
   970  						// vmdk case:  compare prevDisk["vmdk"] and mo.Filename
   971  						if diskName == prevDisk["name"] || diskPath == prevDisk["vmdk"] {
   972  
   973  							prevDisk["key"] = virtualDevice.Key
   974  							prevDisk["uuid"] = diskUuid
   975  
   976  							disks = append(disks, prevDisk)
   977  							break
   978  						}
   979  					}
   980  				}
   981  			}
   982  			log.Printf("[DEBUG] disks: %#v", disks)
   983  		}
   984  	}
   985  	err = d.Set("disk", disks)
   986  	if err != nil {
   987  		return fmt.Errorf("Invalid disks to set: %#v", disks)
   988  	}
   989  
   990  	networkInterfaces := make([]map[string]interface{}, 0)
   991  	for _, v := range mvm.Guest.Net {
   992  		if v.DeviceConfigId >= 0 {
   993  			log.Printf("[DEBUG] v.Network - %#v", v.Network)
   994  			networkInterface := make(map[string]interface{})
   995  			networkInterface["label"] = v.Network
   996  			networkInterface["mac_address"] = v.MacAddress
   997  			for _, ip := range v.IpConfig.IpAddress {
   998  				p := net.ParseIP(ip.IpAddress)
   999  				if p.To4() != nil {
  1000  					log.Printf("[DEBUG] p.String - %#v", p.String())
  1001  					log.Printf("[DEBUG] ip.PrefixLength - %#v", ip.PrefixLength)
  1002  					networkInterface["ipv4_address"] = p.String()
  1003  					networkInterface["ipv4_prefix_length"] = ip.PrefixLength
  1004  				} else if p.To16() != nil {
  1005  					log.Printf("[DEBUG] p.String - %#v", p.String())
  1006  					log.Printf("[DEBUG] ip.PrefixLength - %#v", ip.PrefixLength)
  1007  					networkInterface["ipv6_address"] = p.String()
  1008  					networkInterface["ipv6_prefix_length"] = ip.PrefixLength
  1009  				}
  1010  				log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
  1011  			}
  1012  			log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
  1013  			networkInterfaces = append(networkInterfaces, networkInterface)
  1014  		}
  1015  	}
  1016  	if mvm.Guest.IpStack != nil {
  1017  		for _, v := range mvm.Guest.IpStack {
  1018  			if v.IpRouteConfig != nil && v.IpRouteConfig.IpRoute != nil {
  1019  				for _, route := range v.IpRouteConfig.IpRoute {
  1020  					if route.Gateway.Device != "" {
  1021  						gatewaySetting := ""
  1022  						if route.Network == "::" {
  1023  							gatewaySetting = "ipv6_gateway"
  1024  						} else if route.Network == "0.0.0.0" {
  1025  							gatewaySetting = "ipv4_gateway"
  1026  						}
  1027  						if gatewaySetting != "" {
  1028  							deviceID, err := strconv.Atoi(route.Gateway.Device)
  1029  							if err != nil {
  1030  								log.Printf("[WARN] error at processing %s of device id %#v: %#v", gatewaySetting, route.Gateway.Device, err)
  1031  							} else {
  1032  								log.Printf("[DEBUG] %s of device id %d: %s", gatewaySetting, deviceID, route.Gateway.IpAddress)
  1033  								networkInterfaces[deviceID][gatewaySetting] = route.Gateway.IpAddress
  1034  							}
  1035  						}
  1036  					}
  1037  				}
  1038  			}
  1039  		}
  1040  	}
  1041  	log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces)
  1042  	err = d.Set("network_interface", networkInterfaces)
  1043  	if err != nil {
  1044  		return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces)
  1045  	}
  1046  
  1047  	log.Printf("[DEBUG] ip address: %v", networkInterfaces[0]["ipv4_address"].(string))
  1048  	d.SetConnInfo(map[string]string{
  1049  		"type": "ssh",
  1050  		"host": networkInterfaces[0]["ipv4_address"].(string),
  1051  	})
  1052  
  1053  	var rootDatastore string
  1054  	for _, v := range mvm.Datastore {
  1055  		var md mo.Datastore
  1056  		if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil {
  1057  			return err
  1058  		}
  1059  		if md.Parent.Type == "StoragePod" {
  1060  			var msp mo.StoragePod
  1061  			if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil {
  1062  				return err
  1063  			}
  1064  			rootDatastore = msp.Name
  1065  			log.Printf("[DEBUG] %#v", msp.Name)
  1066  		} else {
  1067  			rootDatastore = md.Name
  1068  			log.Printf("[DEBUG] %#v", md.Name)
  1069  		}
  1070  		break
  1071  	}
  1072  
  1073  	d.Set("datacenter", dc)
  1074  	d.Set("memory", mvm.Summary.Config.MemorySizeMB)
  1075  	d.Set("memory_reservation", mvm.Summary.Config.MemoryReservation)
  1076  	d.Set("cpu", mvm.Summary.Config.NumCpu)
  1077  	d.Set("datastore", rootDatastore)
  1078  	d.Set("uuid", mvm.Summary.Config.Uuid)
  1079  
  1080  	return nil
  1081  }
  1082  
  1083  func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error {
  1084  	client := meta.(*govmomi.Client)
  1085  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
  1086  	if err != nil {
  1087  		return err
  1088  	}
  1089  	finder := find.NewFinder(client.Client, true)
  1090  	finder = finder.SetDatacenter(dc)
  1091  
  1092  	vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string)))
  1093  	if err != nil {
  1094  		return err
  1095  	}
  1096  
  1097  	log.Printf("[INFO] Deleting virtual machine: %s", d.Id())
  1098  	state, err := vm.PowerState(context.TODO())
  1099  	if err != nil {
  1100  		return err
  1101  	}
  1102  
  1103  	if state == types.VirtualMachinePowerStatePoweredOn {
  1104  		task, err := vm.PowerOff(context.TODO())
  1105  		if err != nil {
  1106  			return err
  1107  		}
  1108  
  1109  		err = task.Wait(context.TODO())
  1110  		if err != nil {
  1111  			return err
  1112  		}
  1113  	}
  1114  
  1115  	task, err := vm.Destroy(context.TODO())
  1116  	if err != nil {
  1117  		return err
  1118  	}
  1119  
  1120  	err = task.Wait(context.TODO())
  1121  	if err != nil {
  1122  		return err
  1123  	}
  1124  
  1125  	d.SetId("")
  1126  	return nil
  1127  }
  1128  
  1129  // addHardDisk adds a new Hard Disk to the VirtualMachine.
  1130  func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string, datastore *object.Datastore, diskPath string, controller_type string) error {
  1131  	devices, err := vm.Device(context.TODO())
  1132  	if err != nil {
  1133  		return err
  1134  	}
  1135  	log.Printf("[DEBUG] vm devices: %#v\n", devices)
  1136  
  1137  	var controller types.BaseVirtualController
  1138  	controller, err = devices.FindDiskController(controller_type)
  1139  	if err != nil {
  1140  		log.Printf("[DEBUG] Couldn't find a %v controller.  Creating one..", controller_type)
  1141  
  1142  		var c types.BaseVirtualDevice
  1143  		switch controller_type {
  1144  		case "scsi":
  1145  			// Create scsi controller
  1146  			c, err = devices.CreateSCSIController("scsi")
  1147  			if err != nil {
  1148  				return fmt.Errorf("[ERROR] Failed creating SCSI controller: %v", err)
  1149  			}
  1150  		case "ide":
  1151  			// Create ide controller
  1152  			c, err = devices.CreateIDEController()
  1153  			if err != nil {
  1154  				return fmt.Errorf("[ERROR] Failed creating IDE controller: %v", err)
  1155  			}
  1156  		default:
  1157  			return fmt.Errorf("[ERROR] Unsupported disk controller provided: %v", controller_type)
  1158  		}
  1159  
  1160  		vm.AddDevice(context.TODO(), c)
  1161  		// Update our devices list
  1162  		devices, err := vm.Device(context.TODO())
  1163  		if err != nil {
  1164  			return err
  1165  		}
  1166  		controller, err = devices.FindDiskController(controller_type)
  1167  		if err != nil {
  1168  			log.Printf("[ERROR] Could not find the new %v controller: %v", controller_type, err)
  1169  			return err
  1170  		}
  1171  	}
  1172  
  1173  	log.Printf("[DEBUG] disk controller: %#v\n", controller)
  1174  
  1175  	// TODO Check if diskPath & datastore exist
  1176  	// If diskPath is not specified, pass empty string to CreateDisk()
  1177  	if diskPath == "" {
  1178  		return fmt.Errorf("[ERROR] addHardDisk - No path proided")
  1179  	} else {
  1180  		// TODO Check if diskPath & datastore exist
  1181  		diskPath = fmt.Sprintf("[%v] %v", datastore.Name(), diskPath)
  1182  	}
  1183  	log.Printf("[DEBUG] addHardDisk - diskPath: %v", diskPath)
  1184  	disk := devices.CreateDisk(controller, datastore.Reference(), diskPath)
  1185  
  1186  	existing := devices.SelectByBackingInfo(disk.Backing)
  1187  	log.Printf("[DEBUG] disk: %#v\n", disk)
  1188  
  1189  	if len(existing) == 0 {
  1190  		disk.CapacityInKB = int64(size * 1024 * 1024)
  1191  		if iops != 0 {
  1192  			disk.StorageIOAllocation = &types.StorageIOAllocationInfo{
  1193  				Limit: iops,
  1194  			}
  1195  		}
  1196  		backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
  1197  
  1198  		if diskType == "eager_zeroed" {
  1199  			// eager zeroed thick virtual disk
  1200  			backing.ThinProvisioned = types.NewBool(false)
  1201  			backing.EagerlyScrub = types.NewBool(true)
  1202  		} else if diskType == "thin" {
  1203  			// thin provisioned virtual disk
  1204  			backing.ThinProvisioned = types.NewBool(true)
  1205  		}
  1206  
  1207  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk)
  1208  		log.Printf("[DEBUG] addHardDisk capacity: %#v\n", disk.CapacityInKB)
  1209  
  1210  		return vm.AddDevice(context.TODO(), disk)
  1211  	} else {
  1212  		log.Printf("[DEBUG] addHardDisk: Disk already present.\n")
  1213  
  1214  		return nil
  1215  	}
  1216  }
  1217  
  1218  // addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path.
  1219  func addCdrom(vm *object.VirtualMachine, datastore, path string) error {
  1220  	devices, err := vm.Device(context.TODO())
  1221  	if err != nil {
  1222  		return err
  1223  	}
  1224  	log.Printf("[DEBUG] vm devices: %#v", devices)
  1225  
  1226  	var controller *types.VirtualIDEController
  1227  	controller, err = devices.FindIDEController("")
  1228  	if err != nil {
  1229  		log.Printf("[DEBUG] Couldn't find a ide controller.  Creating one..")
  1230  
  1231  		var c types.BaseVirtualDevice
  1232  		c, err := devices.CreateIDEController()
  1233  		if err != nil {
  1234  			return fmt.Errorf("[ERROR] Failed creating IDE controller: %v", err)
  1235  		}
  1236  
  1237  		if v, ok := c.(*types.VirtualIDEController); ok {
  1238  			controller = v
  1239  		} else {
  1240  			return fmt.Errorf("[ERROR] Controller type could not be asserted")
  1241  		}
  1242  		vm.AddDevice(context.TODO(), c)
  1243  		// Update our devices list
  1244  		devices, err := vm.Device(context.TODO())
  1245  		if err != nil {
  1246  			return err
  1247  		}
  1248  		controller, err = devices.FindIDEController("")
  1249  		if err != nil {
  1250  			log.Printf("[ERROR] Could not find the new disk IDE controller: %v", err)
  1251  			return err
  1252  		}
  1253  	}
  1254  	log.Printf("[DEBUG] ide controller: %#v", controller)
  1255  
  1256  	c, err := devices.CreateCdrom(controller)
  1257  	if err != nil {
  1258  		return err
  1259  	}
  1260  
  1261  	c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path))
  1262  	log.Printf("[DEBUG] addCdrom: %#v", c)
  1263  
  1264  	return vm.AddDevice(context.TODO(), c)
  1265  }
  1266  
  1267  // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device.
  1268  func buildNetworkDevice(f *find.Finder, label, adapterType string, macAddress string) (*types.VirtualDeviceConfigSpec, error) {
  1269  	network, err := f.Network(context.TODO(), "*"+label)
  1270  	if err != nil {
  1271  		return nil, err
  1272  	}
  1273  
  1274  	backing, err := network.EthernetCardBackingInfo(context.TODO())
  1275  	if err != nil {
  1276  		return nil, err
  1277  	}
  1278  
  1279  	var address_type string
  1280  	if macAddress == "" {
  1281  		address_type = string(types.VirtualEthernetCardMacTypeGenerated)
  1282  	} else {
  1283  		address_type = string(types.VirtualEthernetCardMacTypeManual)
  1284  	}
  1285  
  1286  	if adapterType == "vmxnet3" {
  1287  		return &types.VirtualDeviceConfigSpec{
  1288  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
  1289  			Device: &types.VirtualVmxnet3{
  1290  				VirtualVmxnet: types.VirtualVmxnet{
  1291  					VirtualEthernetCard: types.VirtualEthernetCard{
  1292  						VirtualDevice: types.VirtualDevice{
  1293  							Key:     -1,
  1294  							Backing: backing,
  1295  						},
  1296  						AddressType: address_type,
  1297  						MacAddress:  macAddress,
  1298  					},
  1299  				},
  1300  			},
  1301  		}, nil
  1302  	} else if adapterType == "e1000" {
  1303  		return &types.VirtualDeviceConfigSpec{
  1304  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
  1305  			Device: &types.VirtualE1000{
  1306  				VirtualEthernetCard: types.VirtualEthernetCard{
  1307  					VirtualDevice: types.VirtualDevice{
  1308  						Key:     -1,
  1309  						Backing: backing,
  1310  					},
  1311  					AddressType: address_type,
  1312  					MacAddress:  macAddress,
  1313  				},
  1314  			},
  1315  		}, nil
  1316  	} else {
  1317  		return nil, fmt.Errorf("Invalid network adapter type.")
  1318  	}
  1319  }
  1320  
  1321  // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine.
  1322  func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) {
  1323  	var key int32
  1324  	var moveType string
  1325  	if linkedClone {
  1326  		moveType = "createNewChildDiskBacking"
  1327  	} else {
  1328  		moveType = "moveAllDiskBackingsAndDisallowSharing"
  1329  	}
  1330  	log.Printf("[DEBUG] relocate type: [%s]", moveType)
  1331  
  1332  	devices, err := vm.Device(context.TODO())
  1333  	if err != nil {
  1334  		return types.VirtualMachineRelocateSpec{}, err
  1335  	}
  1336  	for _, d := range devices {
  1337  		if devices.Type(d) == "disk" {
  1338  			key = int32(d.GetVirtualDevice().Key)
  1339  		}
  1340  	}
  1341  
  1342  	isThin := initType == "thin"
  1343  	rpr := rp.Reference()
  1344  	dsr := ds.Reference()
  1345  	return types.VirtualMachineRelocateSpec{
  1346  		Datastore:    &dsr,
  1347  		Pool:         &rpr,
  1348  		DiskMoveType: moveType,
  1349  		Disk: []types.VirtualMachineRelocateSpecDiskLocator{
  1350  			{
  1351  				Datastore: dsr,
  1352  				DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
  1353  					DiskMode:        "persistent",
  1354  					ThinProvisioned: types.NewBool(isThin),
  1355  					EagerlyScrub:    types.NewBool(!isThin),
  1356  				},
  1357  				DiskId: key,
  1358  			},
  1359  		},
  1360  	}, nil
  1361  }
  1362  
  1363  // getDatastoreObject gets datastore object.
  1364  func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) {
  1365  	s := object.NewSearchIndex(client.Client)
  1366  	ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name)
  1367  	if err != nil {
  1368  		return types.ManagedObjectReference{}, err
  1369  	}
  1370  	if ref == nil {
  1371  		return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name)
  1372  	}
  1373  	log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref)
  1374  	return ref.Reference(), nil
  1375  }
  1376  
  1377  // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action.
  1378  func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec {
  1379  	vmfr := f.VmFolder.Reference()
  1380  	rpr := rp.Reference()
  1381  	spr := storagePod.Reference()
  1382  
  1383  	sps := types.StoragePlacementSpec{
  1384  		Type:       "create",
  1385  		ConfigSpec: &configSpec,
  1386  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
  1387  			StoragePod: &spr,
  1388  		},
  1389  		Folder:       &vmfr,
  1390  		ResourcePool: &rpr,
  1391  	}
  1392  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
  1393  	return sps
  1394  }
  1395  
  1396  // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action.
  1397  func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec {
  1398  	vmr := vm.Reference()
  1399  	vmfr := f.VmFolder.Reference()
  1400  	rpr := rp.Reference()
  1401  	spr := storagePod.Reference()
  1402  
  1403  	var o mo.VirtualMachine
  1404  	err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o)
  1405  	if err != nil {
  1406  		return types.StoragePlacementSpec{}
  1407  	}
  1408  	ds := object.NewDatastore(c.Client, o.Datastore[0])
  1409  	log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds)
  1410  
  1411  	devices, err := vm.Device(context.TODO())
  1412  	if err != nil {
  1413  		return types.StoragePlacementSpec{}
  1414  	}
  1415  
  1416  	var key int32
  1417  	for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) {
  1418  		key = int32(d.GetVirtualDevice().Key)
  1419  		log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice())
  1420  	}
  1421  
  1422  	sps := types.StoragePlacementSpec{
  1423  		Type: "clone",
  1424  		Vm:   &vmr,
  1425  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
  1426  			StoragePod: &spr,
  1427  		},
  1428  		CloneSpec: &types.VirtualMachineCloneSpec{
  1429  			Location: types.VirtualMachineRelocateSpec{
  1430  				Disk: []types.VirtualMachineRelocateSpecDiskLocator{
  1431  					{
  1432  						Datastore:       ds.Reference(),
  1433  						DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{},
  1434  						DiskId:          key,
  1435  					},
  1436  				},
  1437  				Pool: &rpr,
  1438  			},
  1439  			PowerOn:  false,
  1440  			Template: false,
  1441  		},
  1442  		CloneName: "dummy",
  1443  		Folder:    &vmfr,
  1444  	}
  1445  	return sps
  1446  }
  1447  
  1448  // findDatastore finds Datastore object.
  1449  func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) {
  1450  	var datastore *object.Datastore
  1451  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
  1452  
  1453  	srm := object.NewStorageResourceManager(c.Client)
  1454  	rds, err := srm.RecommendDatastores(context.TODO(), sps)
  1455  	if err != nil {
  1456  		return nil, err
  1457  	}
  1458  	log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds)
  1459  
  1460  	spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction)
  1461  	datastore = object.NewDatastore(c.Client, spa.Destination)
  1462  	log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore)
  1463  
  1464  	return datastore, nil
  1465  }
  1466  
  1467  // createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller.
  1468  func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error {
  1469  	log.Printf("[DEBUG] add cdroms: %v", cdroms)
  1470  	for _, cd := range cdroms {
  1471  		log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore)
  1472  		log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path)
  1473  		err := addCdrom(vm, cd.datastore, cd.path)
  1474  		if err != nil {
  1475  			return err
  1476  		}
  1477  	}
  1478  
  1479  	return nil
  1480  }
  1481  
  1482  func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error {
  1483  	dc, err := getDatacenter(c, vm.datacenter)
  1484  
  1485  	if err != nil {
  1486  		return err
  1487  	}
  1488  	finder := find.NewFinder(c.Client, true)
  1489  	finder = finder.SetDatacenter(dc)
  1490  
  1491  	var template *object.VirtualMachine
  1492  	var template_mo mo.VirtualMachine
  1493  	var vm_mo mo.VirtualMachine
  1494  	if vm.template != "" {
  1495  		template, err = finder.VirtualMachine(context.TODO(), vm.template)
  1496  		if err != nil {
  1497  			return err
  1498  		}
  1499  		log.Printf("[DEBUG] template: %#v", template)
  1500  
  1501  		err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo)
  1502  		if err != nil {
  1503  			return err
  1504  		}
  1505  	}
  1506  
  1507  	var resourcePool *object.ResourcePool
  1508  	if vm.resourcePool == "" {
  1509  		if vm.cluster == "" {
  1510  			resourcePool, err = finder.DefaultResourcePool(context.TODO())
  1511  			if err != nil {
  1512  				return err
  1513  			}
  1514  		} else {
  1515  			resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources")
  1516  			if err != nil {
  1517  				return err
  1518  			}
  1519  		}
  1520  	} else {
  1521  		resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool)
  1522  		if err != nil {
  1523  			return err
  1524  		}
  1525  	}
  1526  	log.Printf("[DEBUG] resource pool: %#v", resourcePool)
  1527  
  1528  	dcFolders, err := dc.Folders(context.TODO())
  1529  	if err != nil {
  1530  		return err
  1531  	}
  1532  	log.Printf("[DEBUG] folder: %#v", vm.folder)
  1533  
  1534  	folder := dcFolders.VmFolder
  1535  	if len(vm.folder) > 0 {
  1536  		si := object.NewSearchIndex(c.Client)
  1537  		folderRef, err := si.FindByInventoryPath(
  1538  			context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder))
  1539  		if err != nil {
  1540  			return fmt.Errorf("Error reading folder %s: %s", vm.folder, err)
  1541  		} else if folderRef == nil {
  1542  			return fmt.Errorf("Cannot find folder %s", vm.folder)
  1543  		} else {
  1544  			folder = folderRef.(*object.Folder)
  1545  		}
  1546  	}
  1547  
  1548  	// make config spec
  1549  	configSpec := types.VirtualMachineConfigSpec{
  1550  		Name:              vm.name,
  1551  		NumCPUs:           vm.vcpu,
  1552  		NumCoresPerSocket: 1,
  1553  		MemoryMB:          vm.memoryMb,
  1554  		MemoryAllocation: &types.ResourceAllocationInfo{
  1555  			Reservation: vm.memoryAllocation.reservation,
  1556  		},
  1557  		Flags: &types.VirtualMachineFlagInfo{
  1558  			DiskUuidEnabled: &vm.enableDiskUUID,
  1559  		},
  1560  	}
  1561  	if vm.template == "" {
  1562  		configSpec.GuestId = "otherLinux64Guest"
  1563  	}
  1564  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1565  
  1566  	// make ExtraConfig
  1567  	log.Printf("[DEBUG] virtual machine Extra Config spec start")
  1568  	if len(vm.customConfigurations) > 0 {
  1569  		var ov []types.BaseOptionValue
  1570  		for k, v := range vm.customConfigurations {
  1571  			key := k
  1572  			value := v
  1573  			o := types.OptionValue{
  1574  				Key:   key,
  1575  				Value: &value,
  1576  			}
  1577  			log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v)
  1578  			ov = append(ov, &o)
  1579  		}
  1580  		configSpec.ExtraConfig = ov
  1581  		log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
  1582  	}
  1583  
  1584  	var datastore *object.Datastore
  1585  	if vm.datastore == "" {
  1586  		datastore, err = finder.DefaultDatastore(context.TODO())
  1587  		if err != nil {
  1588  			return err
  1589  		}
  1590  	} else {
  1591  		datastore, err = finder.Datastore(context.TODO(), vm.datastore)
  1592  		if err != nil {
  1593  			// TODO: datastore cluster support in govmomi finder function
  1594  			d, err := getDatastoreObject(c, dcFolders, vm.datastore)
  1595  			if err != nil {
  1596  				return err
  1597  			}
  1598  
  1599  			if d.Type == "StoragePod" {
  1600  				sp := object.StoragePod{
  1601  					Folder: object.NewFolder(c.Client, d),
  1602  				}
  1603  
  1604  				var sps types.StoragePlacementSpec
  1605  				if vm.template != "" {
  1606  					sps = buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp)
  1607  				} else {
  1608  					sps = buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec)
  1609  				}
  1610  
  1611  				datastore, err = findDatastore(c, sps)
  1612  				if err != nil {
  1613  					return err
  1614  				}
  1615  			} else {
  1616  				datastore = object.NewDatastore(c.Client, d)
  1617  			}
  1618  		}
  1619  	}
  1620  
  1621  	log.Printf("[DEBUG] datastore: %#v", datastore)
  1622  
  1623  	// network
  1624  	networkDevices := []types.BaseVirtualDeviceConfigSpec{}
  1625  	networkConfigs := []types.CustomizationAdapterMapping{}
  1626  	for _, network := range vm.networkInterfaces {
  1627  		// network device
  1628  		var networkDeviceType string
  1629  		if vm.template == "" {
  1630  			networkDeviceType = "e1000"
  1631  		} else {
  1632  			networkDeviceType = "vmxnet3"
  1633  		}
  1634  		nd, err := buildNetworkDevice(finder, network.label, networkDeviceType, network.macAddress)
  1635  		if err != nil {
  1636  			return err
  1637  		}
  1638  		log.Printf("[DEBUG] network device: %+v", nd.Device)
  1639  		networkDevices = append(networkDevices, nd)
  1640  
  1641  		if vm.template != "" {
  1642  			var ipSetting types.CustomizationIPSettings
  1643  			if network.ipv4Address == "" {
  1644  				ipSetting.Ip = &types.CustomizationDhcpIpGenerator{}
  1645  			} else {
  1646  				if network.ipv4PrefixLength == 0 {
  1647  					return fmt.Errorf("Error: ipv4_prefix_length argument is empty.")
  1648  				}
  1649  				m := net.CIDRMask(network.ipv4PrefixLength, 32)
  1650  				sm := net.IPv4(m[0], m[1], m[2], m[3])
  1651  				subnetMask := sm.String()
  1652  				log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway)
  1653  				log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address)
  1654  				log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength)
  1655  				log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask)
  1656  				ipSetting.Gateway = []string{
  1657  					network.ipv4Gateway,
  1658  				}
  1659  				ipSetting.Ip = &types.CustomizationFixedIp{
  1660  					IpAddress: network.ipv4Address,
  1661  				}
  1662  				ipSetting.SubnetMask = subnetMask
  1663  			}
  1664  
  1665  			ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{}
  1666  			if network.ipv6Address == "" {
  1667  				ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
  1668  					&types.CustomizationDhcpIpV6Generator{},
  1669  				}
  1670  			} else {
  1671  				log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway)
  1672  				log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address)
  1673  				log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength)
  1674  
  1675  				ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
  1676  					&types.CustomizationFixedIpV6{
  1677  						IpAddress:  network.ipv6Address,
  1678  						SubnetMask: int32(network.ipv6PrefixLength),
  1679  					},
  1680  				}
  1681  				ipv6Spec.Gateway = []string{network.ipv6Gateway}
  1682  			}
  1683  			ipSetting.IpV6Spec = ipv6Spec
  1684  
  1685  			// network config
  1686  			config := types.CustomizationAdapterMapping{
  1687  				Adapter: ipSetting,
  1688  			}
  1689  			networkConfigs = append(networkConfigs, config)
  1690  		}
  1691  	}
  1692  	log.Printf("[DEBUG] network devices: %#v", networkDevices)
  1693  	log.Printf("[DEBUG] network configs: %#v", networkConfigs)
  1694  
  1695  	var task *object.Task
  1696  	if vm.template == "" {
  1697  		var mds mo.Datastore
  1698  		if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil {
  1699  			return err
  1700  		}
  1701  		log.Printf("[DEBUG] datastore: %#v", mds.Name)
  1702  		scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi")
  1703  		if err != nil {
  1704  			log.Printf("[ERROR] %s", err)
  1705  		}
  1706  
  1707  		configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{
  1708  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
  1709  			Device:    scsi,
  1710  		})
  1711  
  1712  		configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)}
  1713  
  1714  		task, err = folder.CreateVM(context.TODO(), configSpec, resourcePool, nil)
  1715  		if err != nil {
  1716  			log.Printf("[ERROR] %s", err)
  1717  		}
  1718  
  1719  		err = task.Wait(context.TODO())
  1720  		if err != nil {
  1721  			log.Printf("[ERROR] %s", err)
  1722  		}
  1723  
  1724  	} else {
  1725  
  1726  		relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType)
  1727  		if err != nil {
  1728  			return err
  1729  		}
  1730  
  1731  		log.Printf("[DEBUG] relocate spec: %v", relocateSpec)
  1732  
  1733  		// make vm clone spec
  1734  		cloneSpec := types.VirtualMachineCloneSpec{
  1735  			Location: relocateSpec,
  1736  			Template: false,
  1737  			Config:   &configSpec,
  1738  			PowerOn:  false,
  1739  		}
  1740  		if vm.linkedClone {
  1741  			if template_mo.Snapshot == nil {
  1742  				return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots")
  1743  			}
  1744  			cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot
  1745  		}
  1746  		log.Printf("[DEBUG] clone spec: %v", cloneSpec)
  1747  
  1748  		task, err = template.Clone(context.TODO(), folder, vm.name, cloneSpec)
  1749  		if err != nil {
  1750  			return err
  1751  		}
  1752  	}
  1753  
  1754  	err = task.Wait(context.TODO())
  1755  	if err != nil {
  1756  		log.Printf("[ERROR] %s", err)
  1757  	}
  1758  
  1759  	newVM, err := finder.VirtualMachine(context.TODO(), vm.Path())
  1760  	if err != nil {
  1761  		return err
  1762  	}
  1763  	log.Printf("[DEBUG] new vm: %v", newVM)
  1764  
  1765  	devices, err := newVM.Device(context.TODO())
  1766  	if err != nil {
  1767  		log.Printf("[DEBUG] Template devices can't be found")
  1768  		return err
  1769  	}
  1770  
  1771  	for _, dvc := range devices {
  1772  		// Issue 3559/3560: Delete all ethernet devices to add the correct ones later
  1773  		if devices.Type(dvc) == "ethernet" {
  1774  			err := newVM.RemoveDevice(context.TODO(), false, dvc)
  1775  			if err != nil {
  1776  				return err
  1777  			}
  1778  		}
  1779  	}
  1780  	// Add Network devices
  1781  	for _, dvc := range networkDevices {
  1782  		err := newVM.AddDevice(
  1783  			context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device)
  1784  		if err != nil {
  1785  			return err
  1786  		}
  1787  	}
  1788  
  1789  	// Create the cdroms if needed.
  1790  	if err := createCdroms(newVM, vm.cdroms); err != nil {
  1791  		return err
  1792  	}
  1793  
  1794  	newVM.Properties(context.TODO(), newVM.Reference(), []string{"summary", "config"}, &vm_mo)
  1795  	firstDisk := 0
  1796  	if vm.template != "" {
  1797  		firstDisk++
  1798  	}
  1799  	for i := firstDisk; i < len(vm.hardDisks); i++ {
  1800  		log.Printf("[DEBUG] disk index: %v", i)
  1801  
  1802  		var diskPath string
  1803  		switch {
  1804  		case vm.hardDisks[i].vmdkPath != "":
  1805  			diskPath = vm.hardDisks[i].vmdkPath
  1806  		case vm.hardDisks[i].name != "":
  1807  			snapshotFullDir := vm_mo.Config.Files.SnapshotDirectory
  1808  			split := strings.Split(snapshotFullDir, " ")
  1809  			if len(split) != 2 {
  1810  				return fmt.Errorf("[ERROR] setupVirtualMachine - failed to split snapshot directory: %v", snapshotFullDir)
  1811  			}
  1812  			vmWorkingPath := split[1]
  1813  			diskPath = vmWorkingPath + vm.hardDisks[i].name
  1814  		default:
  1815  			return fmt.Errorf("[ERROR] setupVirtualMachine - Neither vmdk path nor vmdk name was given: %#v", vm.hardDisks[i])
  1816  		}
  1817  
  1818  		err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, diskPath, vm.hardDisks[i].controller)
  1819  		if err != nil {
  1820  			return err
  1821  		}
  1822  	}
  1823  
  1824  	if vm.skipCustomization || vm.template == "" {
  1825  		log.Printf("[DEBUG] VM customization skipped")
  1826  	} else {
  1827  		var identity_options types.BaseCustomizationIdentitySettings
  1828  		if strings.HasPrefix(template_mo.Config.GuestId, "win") {
  1829  			var timeZone int
  1830  			if vm.timeZone == "Etc/UTC" {
  1831  				vm.timeZone = "085"
  1832  			}
  1833  			timeZone, err := strconv.Atoi(vm.timeZone)
  1834  			if err != nil {
  1835  				return fmt.Errorf("Error converting TimeZone: %s", err)
  1836  			}
  1837  
  1838  			guiUnattended := types.CustomizationGuiUnattended{
  1839  				AutoLogon:      false,
  1840  				AutoLogonCount: 1,
  1841  				TimeZone:       int32(timeZone),
  1842  			}
  1843  
  1844  			customIdentification := types.CustomizationIdentification{}
  1845  
  1846  			userData := types.CustomizationUserData{
  1847  				ComputerName: &types.CustomizationFixedName{
  1848  					Name: strings.Split(vm.name, ".")[0],
  1849  				},
  1850  				ProductId: vm.windowsOptionalConfig.productKey,
  1851  				FullName:  "terraform",
  1852  				OrgName:   "terraform",
  1853  			}
  1854  
  1855  			if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" {
  1856  				customIdentification.DomainAdminPassword = &types.CustomizationPassword{
  1857  					PlainText: true,
  1858  					Value:     vm.windowsOptionalConfig.domainUserPassword,
  1859  				}
  1860  				customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser
  1861  				customIdentification.JoinDomain = vm.windowsOptionalConfig.domain
  1862  			}
  1863  
  1864  			if vm.windowsOptionalConfig.adminPassword != "" {
  1865  				guiUnattended.Password = &types.CustomizationPassword{
  1866  					PlainText: true,
  1867  					Value:     vm.windowsOptionalConfig.adminPassword,
  1868  				}
  1869  			}
  1870  
  1871  			identity_options = &types.CustomizationSysprep{
  1872  				GuiUnattended:  guiUnattended,
  1873  				Identification: customIdentification,
  1874  				UserData:       userData,
  1875  			}
  1876  		} else {
  1877  			identity_options = &types.CustomizationLinuxPrep{
  1878  				HostName: &types.CustomizationFixedName{
  1879  					Name: strings.Split(vm.name, ".")[0],
  1880  				},
  1881  				Domain:     vm.domain,
  1882  				TimeZone:   vm.timeZone,
  1883  				HwClockUTC: types.NewBool(true),
  1884  			}
  1885  		}
  1886  
  1887  		// create CustomizationSpec
  1888  		customSpec := types.CustomizationSpec{
  1889  			Identity: identity_options,
  1890  			GlobalIPSettings: types.CustomizationGlobalIPSettings{
  1891  				DnsSuffixList: vm.dnsSuffixes,
  1892  				DnsServerList: vm.dnsServers,
  1893  			},
  1894  			NicSettingMap: networkConfigs,
  1895  		}
  1896  		log.Printf("[DEBUG] custom spec: %v", customSpec)
  1897  
  1898  		log.Printf("[DEBUG] VM customization starting")
  1899  		taskb, err := newVM.Customize(context.TODO(), customSpec)
  1900  		if err != nil {
  1901  			return err
  1902  		}
  1903  		_, err = taskb.WaitForResult(context.TODO(), nil)
  1904  		if err != nil {
  1905  			return err
  1906  		}
  1907  		log.Printf("[DEBUG] VM customization finished")
  1908  	}
  1909  
  1910  	if vm.hasBootableVmdk || vm.template != "" {
  1911  		newVM.PowerOn(context.TODO())
  1912  	}
  1913  	return nil
  1914  }