github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/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  }
    40  
    41  type hardDisk struct {
    42  	size     int64
    43  	iops     int64
    44  	initType string
    45  	vmdkPath string
    46  }
    47  
    48  //Additional options Vsphere can use clones of windows machines
    49  type windowsOptConfig struct {
    50  	productKey         string
    51  	adminPassword      string
    52  	domainUser         string
    53  	domain             string
    54  	domainUserPassword string
    55  }
    56  
    57  type cdrom struct {
    58  	datastore string
    59  	path      string
    60  }
    61  
    62  type memoryAllocation struct {
    63  	reservation int64
    64  }
    65  
    66  type virtualMachine struct {
    67  	name                  string
    68  	folder                string
    69  	datacenter            string
    70  	cluster               string
    71  	resourcePool          string
    72  	datastore             string
    73  	vcpu                  int32
    74  	memoryMb              int64
    75  	memoryAllocation      memoryAllocation
    76  	template              string
    77  	networkInterfaces     []networkInterface
    78  	hardDisks             []hardDisk
    79  	cdroms                []cdrom
    80  	domain                string
    81  	timeZone              string
    82  	dnsSuffixes           []string
    83  	dnsServers            []string
    84  	bootableVmdk          bool
    85  	linkedClone           bool
    86  	skipCustomization     bool
    87  	windowsOptionalConfig windowsOptConfig
    88  	customConfigurations  map[string](types.AnyType)
    89  }
    90  
    91  func (v virtualMachine) Path() string {
    92  	return vmPath(v.folder, v.name)
    93  }
    94  
    95  func vmPath(folder string, name string) string {
    96  	var path string
    97  	if len(folder) > 0 {
    98  		path += folder + "/"
    99  	}
   100  	return path + name
   101  }
   102  
   103  func resourceVSphereVirtualMachine() *schema.Resource {
   104  	return &schema.Resource{
   105  		Create: resourceVSphereVirtualMachineCreate,
   106  		Read:   resourceVSphereVirtualMachineRead,
   107  		Update: resourceVSphereVirtualMachineUpdate,
   108  		Delete: resourceVSphereVirtualMachineDelete,
   109  
   110  		Schema: map[string]*schema.Schema{
   111  			"name": &schema.Schema{
   112  				Type:     schema.TypeString,
   113  				Required: true,
   114  				ForceNew: true,
   115  			},
   116  
   117  			"folder": &schema.Schema{
   118  				Type:     schema.TypeString,
   119  				Optional: true,
   120  				ForceNew: true,
   121  			},
   122  
   123  			"vcpu": &schema.Schema{
   124  				Type:     schema.TypeInt,
   125  				Required: true,
   126  			},
   127  
   128  			"memory": &schema.Schema{
   129  				Type:     schema.TypeInt,
   130  				Required: true,
   131  			},
   132  
   133  			"memory_reservation": &schema.Schema{
   134  				Type:     schema.TypeInt,
   135  				Optional: true,
   136  				Default:  0,
   137  				ForceNew: true,
   138  			},
   139  
   140  			"datacenter": &schema.Schema{
   141  				Type:     schema.TypeString,
   142  				Optional: true,
   143  				ForceNew: true,
   144  			},
   145  
   146  			"cluster": &schema.Schema{
   147  				Type:     schema.TypeString,
   148  				Optional: true,
   149  				ForceNew: true,
   150  			},
   151  
   152  			"resource_pool": &schema.Schema{
   153  				Type:     schema.TypeString,
   154  				Optional: true,
   155  				ForceNew: true,
   156  			},
   157  
   158  			"linked_clone": &schema.Schema{
   159  				Type:     schema.TypeBool,
   160  				Optional: true,
   161  				Default:  false,
   162  				ForceNew: true,
   163  			},
   164  			"gateway": &schema.Schema{
   165  				Type:       schema.TypeString,
   166  				Optional:   true,
   167  				ForceNew:   true,
   168  				Deprecated: "Please use network_interface.ipv4_gateway",
   169  			},
   170  
   171  			"domain": &schema.Schema{
   172  				Type:     schema.TypeString,
   173  				Optional: true,
   174  				ForceNew: true,
   175  				Default:  "vsphere.local",
   176  			},
   177  
   178  			"time_zone": &schema.Schema{
   179  				Type:     schema.TypeString,
   180  				Optional: true,
   181  				ForceNew: true,
   182  				Default:  "Etc/UTC",
   183  			},
   184  
   185  			"dns_suffixes": &schema.Schema{
   186  				Type:     schema.TypeList,
   187  				Optional: true,
   188  				Elem:     &schema.Schema{Type: schema.TypeString},
   189  				ForceNew: true,
   190  			},
   191  
   192  			"dns_servers": &schema.Schema{
   193  				Type:     schema.TypeList,
   194  				Optional: true,
   195  				Elem:     &schema.Schema{Type: schema.TypeString},
   196  				ForceNew: true,
   197  			},
   198  
   199  			"skip_customization": &schema.Schema{
   200  				Type:     schema.TypeBool,
   201  				Optional: true,
   202  				ForceNew: true,
   203  				Default:  false,
   204  			},
   205  
   206  			"custom_configuration_parameters": &schema.Schema{
   207  				Type:     schema.TypeMap,
   208  				Optional: true,
   209  				ForceNew: true,
   210  			},
   211  			"windows_opt_config": &schema.Schema{
   212  				Type:     schema.TypeList,
   213  				Optional: true,
   214  				ForceNew: true,
   215  				Elem: &schema.Resource{
   216  					Schema: map[string]*schema.Schema{
   217  						"product_key": &schema.Schema{
   218  							Type:     schema.TypeString,
   219  							Required: true,
   220  							ForceNew: true,
   221  						},
   222  
   223  						"admin_password": &schema.Schema{
   224  							Type:     schema.TypeString,
   225  							Optional: true,
   226  							ForceNew: true,
   227  						},
   228  
   229  						"domain_user": &schema.Schema{
   230  							Type:     schema.TypeString,
   231  							Optional: true,
   232  							ForceNew: true,
   233  						},
   234  
   235  						"domain": &schema.Schema{
   236  							Type:     schema.TypeString,
   237  							Optional: true,
   238  							ForceNew: true,
   239  						},
   240  
   241  						"domain_user_password": &schema.Schema{
   242  							Type:     schema.TypeString,
   243  							Optional: true,
   244  							ForceNew: true,
   245  						},
   246  					},
   247  				},
   248  			},
   249  
   250  			"network_interface": &schema.Schema{
   251  				Type:     schema.TypeList,
   252  				Required: true,
   253  				ForceNew: true,
   254  				Elem: &schema.Resource{
   255  					Schema: map[string]*schema.Schema{
   256  						"label": &schema.Schema{
   257  							Type:     schema.TypeString,
   258  							Required: true,
   259  							ForceNew: true,
   260  						},
   261  
   262  						"ip_address": &schema.Schema{
   263  							Type:       schema.TypeString,
   264  							Optional:   true,
   265  							Computed:   true,
   266  							Deprecated: "Please use ipv4_address",
   267  						},
   268  
   269  						"subnet_mask": &schema.Schema{
   270  							Type:       schema.TypeString,
   271  							Optional:   true,
   272  							Computed:   true,
   273  							Deprecated: "Please use ipv4_prefix_length",
   274  						},
   275  
   276  						"ipv4_address": &schema.Schema{
   277  							Type:     schema.TypeString,
   278  							Optional: true,
   279  							Computed: true,
   280  						},
   281  
   282  						"ipv4_prefix_length": &schema.Schema{
   283  							Type:     schema.TypeInt,
   284  							Optional: true,
   285  							Computed: true,
   286  						},
   287  
   288  						"ipv4_gateway": &schema.Schema{
   289  							Type:     schema.TypeString,
   290  							Optional: true,
   291  							Computed: true,
   292  						},
   293  
   294  						"ipv6_address": &schema.Schema{
   295  							Type:     schema.TypeString,
   296  							Optional: true,
   297  							Computed: true,
   298  						},
   299  
   300  						"ipv6_prefix_length": &schema.Schema{
   301  							Type:     schema.TypeInt,
   302  							Optional: true,
   303  							Computed: true,
   304  						},
   305  
   306  						"ipv6_gateway": &schema.Schema{
   307  							Type:     schema.TypeString,
   308  							Optional: true,
   309  							Computed: true,
   310  						},
   311  
   312  						"adapter_type": &schema.Schema{
   313  							Type:     schema.TypeString,
   314  							Optional: true,
   315  							ForceNew: true,
   316  						},
   317  					},
   318  				},
   319  			},
   320  
   321  			"disk": &schema.Schema{
   322  				Type:     schema.TypeList,
   323  				Required: true,
   324  				ForceNew: true,
   325  				Elem: &schema.Resource{
   326  					Schema: map[string]*schema.Schema{
   327  						"template": &schema.Schema{
   328  							Type:     schema.TypeString,
   329  							Optional: true,
   330  							ForceNew: true,
   331  						},
   332  
   333  						"type": &schema.Schema{
   334  							Type:     schema.TypeString,
   335  							Optional: true,
   336  							ForceNew: true,
   337  							Default:  "eager_zeroed",
   338  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   339  								value := v.(string)
   340  								if value != "thin" && value != "eager_zeroed" {
   341  									errors = append(errors, fmt.Errorf(
   342  										"only 'thin' and 'eager_zeroed' are supported values for 'type'"))
   343  								}
   344  								return
   345  							},
   346  						},
   347  
   348  						"datastore": &schema.Schema{
   349  							Type:     schema.TypeString,
   350  							Optional: true,
   351  							ForceNew: true,
   352  						},
   353  
   354  						"size": &schema.Schema{
   355  							Type:     schema.TypeInt,
   356  							Optional: true,
   357  							ForceNew: true,
   358  						},
   359  
   360  						"iops": &schema.Schema{
   361  							Type:     schema.TypeInt,
   362  							Optional: true,
   363  							ForceNew: true,
   364  						},
   365  
   366  						"vmdk": &schema.Schema{
   367  							// TODO: Add ValidateFunc to confirm path exists
   368  							Type:     schema.TypeString,
   369  							Optional: true,
   370  							ForceNew: true,
   371  							Default:  "",
   372  						},
   373  
   374  						"bootable": &schema.Schema{
   375  							Type:     schema.TypeBool,
   376  							Optional: true,
   377  							Default:  false,
   378  							ForceNew: true,
   379  						},
   380  					},
   381  				},
   382  			},
   383  
   384  			"cdrom": &schema.Schema{
   385  				Type:     schema.TypeList,
   386  				Optional: true,
   387  				ForceNew: true,
   388  				Elem: &schema.Resource{
   389  					Schema: map[string]*schema.Schema{
   390  						"datastore": &schema.Schema{
   391  							Type:     schema.TypeString,
   392  							Required: true,
   393  							ForceNew: true,
   394  						},
   395  
   396  						"path": &schema.Schema{
   397  							Type:     schema.TypeString,
   398  							Required: true,
   399  							ForceNew: true,
   400  						},
   401  					},
   402  				},
   403  			},
   404  		},
   405  	}
   406  }
   407  
   408  func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{}) error {
   409  	// flag if changes have to be applied
   410  	hasChanges := false
   411  	// flag if changes have to be done when powered off
   412  	rebootRequired := false
   413  
   414  	// make config spec
   415  	configSpec := types.VirtualMachineConfigSpec{}
   416  
   417  	if d.HasChange("vcpu") {
   418  		configSpec.NumCPUs = int32(d.Get("vcpu").(int))
   419  		hasChanges = true
   420  		rebootRequired = true
   421  	}
   422  
   423  	if d.HasChange("memory") {
   424  		configSpec.MemoryMB = int64(d.Get("memory").(int))
   425  		hasChanges = true
   426  		rebootRequired = true
   427  	}
   428  
   429  	// do nothing if there are no changes
   430  	if !hasChanges {
   431  		return nil
   432  	}
   433  
   434  	client := meta.(*govmomi.Client)
   435  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   436  	if err != nil {
   437  		return err
   438  	}
   439  	finder := find.NewFinder(client.Client, true)
   440  	finder = finder.SetDatacenter(dc)
   441  
   442  	vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string)))
   443  	if err != nil {
   444  		return err
   445  	}
   446  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
   447  
   448  	if rebootRequired {
   449  		log.Printf("[INFO] Shutting down virtual machine: %s", d.Id())
   450  
   451  		task, err := vm.PowerOff(context.TODO())
   452  		if err != nil {
   453  			return err
   454  		}
   455  
   456  		err = task.Wait(context.TODO())
   457  		if err != nil {
   458  			return err
   459  		}
   460  	}
   461  
   462  	log.Printf("[INFO] Reconfiguring virtual machine: %s", d.Id())
   463  
   464  	task, err := vm.Reconfigure(context.TODO(), configSpec)
   465  	if err != nil {
   466  		log.Printf("[ERROR] %s", err)
   467  	}
   468  
   469  	err = task.Wait(context.TODO())
   470  	if err != nil {
   471  		log.Printf("[ERROR] %s", err)
   472  	}
   473  
   474  	if rebootRequired {
   475  		task, err = vm.PowerOn(context.TODO())
   476  		if err != nil {
   477  			return err
   478  		}
   479  
   480  		err = task.Wait(context.TODO())
   481  		if err != nil {
   482  			log.Printf("[ERROR] %s", err)
   483  		}
   484  	}
   485  
   486  	ip, err := vm.WaitForIP(context.TODO())
   487  	if err != nil {
   488  		return err
   489  	}
   490  	log.Printf("[DEBUG] ip address: %v", ip)
   491  
   492  	return resourceVSphereVirtualMachineRead(d, meta)
   493  }
   494  
   495  func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error {
   496  	client := meta.(*govmomi.Client)
   497  
   498  	vm := virtualMachine{
   499  		name:     d.Get("name").(string),
   500  		vcpu:     int32(d.Get("vcpu").(int)),
   501  		memoryMb: int64(d.Get("memory").(int)),
   502  		memoryAllocation: memoryAllocation{
   503  			reservation: int64(d.Get("memory_reservation").(int)),
   504  		},
   505  	}
   506  
   507  	if v, ok := d.GetOk("folder"); ok {
   508  		vm.folder = v.(string)
   509  	}
   510  
   511  	if v, ok := d.GetOk("datacenter"); ok {
   512  		vm.datacenter = v.(string)
   513  	}
   514  
   515  	if v, ok := d.GetOk("cluster"); ok {
   516  		vm.cluster = v.(string)
   517  	}
   518  
   519  	if v, ok := d.GetOk("resource_pool"); ok {
   520  		vm.resourcePool = v.(string)
   521  	}
   522  
   523  	if v, ok := d.GetOk("domain"); ok {
   524  		vm.domain = v.(string)
   525  	}
   526  
   527  	if v, ok := d.GetOk("time_zone"); ok {
   528  		vm.timeZone = v.(string)
   529  	}
   530  
   531  	if v, ok := d.GetOk("linked_clone"); ok {
   532  		vm.linkedClone = v.(bool)
   533  	}
   534  
   535  	if v, ok := d.GetOk("skip_customization"); ok {
   536  		vm.skipCustomization = v.(bool)
   537  	}
   538  
   539  	if raw, ok := d.GetOk("dns_suffixes"); ok {
   540  		for _, v := range raw.([]interface{}) {
   541  			vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string))
   542  		}
   543  	} else {
   544  		vm.dnsSuffixes = DefaultDNSSuffixes
   545  	}
   546  
   547  	if raw, ok := d.GetOk("dns_servers"); ok {
   548  		for _, v := range raw.([]interface{}) {
   549  			vm.dnsServers = append(vm.dnsServers, v.(string))
   550  		}
   551  	} else {
   552  		vm.dnsServers = DefaultDNSServers
   553  	}
   554  
   555  	if vL, ok := d.GetOk("custom_configuration_parameters"); ok {
   556  		if custom_configs, ok := vL.(map[string]interface{}); ok {
   557  			custom := make(map[string]types.AnyType)
   558  			for k, v := range custom_configs {
   559  				custom[k] = v
   560  			}
   561  			vm.customConfigurations = custom
   562  			log.Printf("[DEBUG] custom_configuration_parameters init: %v", vm.customConfigurations)
   563  		}
   564  	}
   565  
   566  	if vL, ok := d.GetOk("network_interface"); ok {
   567  		networks := make([]networkInterface, len(vL.([]interface{})))
   568  		for i, v := range vL.([]interface{}) {
   569  			network := v.(map[string]interface{})
   570  			networks[i].label = network["label"].(string)
   571  			if v, ok := network["ip_address"].(string); ok && v != "" {
   572  				networks[i].ipv4Address = v
   573  			}
   574  			if v, ok := d.GetOk("gateway"); ok {
   575  				networks[i].ipv4Gateway = v.(string)
   576  			}
   577  			if v, ok := network["subnet_mask"].(string); ok && v != "" {
   578  				ip := net.ParseIP(v).To4()
   579  				if ip != nil {
   580  					mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3])
   581  					pl, _ := mask.Size()
   582  					networks[i].ipv4PrefixLength = pl
   583  				} else {
   584  					return fmt.Errorf("subnet_mask parameter is invalid.")
   585  				}
   586  			}
   587  			if v, ok := network["ipv4_address"].(string); ok && v != "" {
   588  				networks[i].ipv4Address = v
   589  			}
   590  			if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 {
   591  				networks[i].ipv4PrefixLength = v
   592  			}
   593  			if v, ok := network["ipv4_gateway"].(string); ok && v != "" {
   594  				networks[i].ipv4Gateway = v
   595  			}
   596  			if v, ok := network["ipv6_address"].(string); ok && v != "" {
   597  				networks[i].ipv6Address = v
   598  			}
   599  			if v, ok := network["ipv6_prefix_length"].(int); ok && v != 0 {
   600  				networks[i].ipv6PrefixLength = v
   601  			}
   602  			if v, ok := network["ipv6_gateway"].(string); ok && v != "" {
   603  				networks[i].ipv6Gateway = v
   604  			}
   605  		}
   606  		vm.networkInterfaces = networks
   607  		log.Printf("[DEBUG] network_interface init: %v", networks)
   608  	}
   609  
   610  	if vL, ok := d.GetOk("windows_opt_config"); ok {
   611  		var winOpt windowsOptConfig
   612  		custom_configs := (vL.([]interface{}))[0].(map[string]interface{})
   613  		if v, ok := custom_configs["admin_password"].(string); ok && v != "" {
   614  			winOpt.adminPassword = v
   615  		}
   616  		if v, ok := custom_configs["domain"].(string); ok && v != "" {
   617  			winOpt.domain = v
   618  		}
   619  		if v, ok := custom_configs["domain_user"].(string); ok && v != "" {
   620  			winOpt.domainUser = v
   621  		}
   622  		if v, ok := custom_configs["product_key"].(string); ok && v != "" {
   623  			winOpt.productKey = v
   624  		}
   625  		if v, ok := custom_configs["domain_user_password"].(string); ok && v != "" {
   626  			winOpt.domainUserPassword = v
   627  		}
   628  		vm.windowsOptionalConfig = winOpt
   629  		log.Printf("[DEBUG] windows config init: %v", winOpt)
   630  	}
   631  
   632  	if vL, ok := d.GetOk("disk"); ok {
   633  		disks := make([]hardDisk, len(vL.([]interface{})))
   634  		for i, v := range vL.([]interface{}) {
   635  			disk := v.(map[string]interface{})
   636  			if i == 0 {
   637  				if v, ok := disk["template"].(string); ok && v != "" {
   638  					vm.template = v
   639  				} else {
   640  					if v, ok := disk["size"].(int); ok && v != 0 {
   641  						disks[i].size = int64(v)
   642  					} else if v, ok := disk["vmdk"].(string); ok && v != "" {
   643  						disks[i].vmdkPath = v
   644  						if v, ok := disk["bootable"].(bool); ok {
   645  							vm.bootableVmdk = v
   646  						}
   647  					} else {
   648  						return fmt.Errorf("template, size, or vmdk argument is required")
   649  					}
   650  				}
   651  				if v, ok := disk["datastore"].(string); ok && v != "" {
   652  					vm.datastore = v
   653  				}
   654  			} else {
   655  				if v, ok := disk["size"].(int); ok && v != 0 {
   656  					disks[i].size = int64(v)
   657  				} else if v, ok := disk["vmdk"].(string); ok && v != "" {
   658  					disks[i].vmdkPath = v
   659  				} else {
   660  					return fmt.Errorf("size or vmdk argument is required")
   661  				}
   662  
   663  			}
   664  			if v, ok := disk["iops"].(int); ok && v != 0 {
   665  				disks[i].iops = int64(v)
   666  			}
   667  			if v, ok := disk["type"].(string); ok && v != "" {
   668  				disks[i].initType = v
   669  			}
   670  		}
   671  		vm.hardDisks = disks
   672  		log.Printf("[DEBUG] disk init: %v", disks)
   673  	}
   674  
   675  	if vL, ok := d.GetOk("cdrom"); ok {
   676  		cdroms := make([]cdrom, len(vL.([]interface{})))
   677  		for i, v := range vL.([]interface{}) {
   678  			c := v.(map[string]interface{})
   679  			if v, ok := c["datastore"].(string); ok && v != "" {
   680  				cdroms[i].datastore = v
   681  			} else {
   682  				return fmt.Errorf("Datastore argument must be specified when attaching a cdrom image.")
   683  			}
   684  			if v, ok := c["path"].(string); ok && v != "" {
   685  				cdroms[i].path = v
   686  			} else {
   687  				return fmt.Errorf("Path argument must be specified when attaching a cdrom image.")
   688  			}
   689  		}
   690  		vm.cdroms = cdroms
   691  		log.Printf("[DEBUG] cdrom init: %v", cdroms)
   692  	}
   693  
   694  	err := vm.setupVirtualMachine(client)
   695  	if err != nil {
   696  		return err
   697  	}
   698  
   699  	d.SetId(vm.Path())
   700  	log.Printf("[INFO] Created virtual machine: %s", d.Id())
   701  
   702  	return resourceVSphereVirtualMachineRead(d, meta)
   703  }
   704  
   705  func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error {
   706  	log.Printf("[DEBUG] reading virtual machine: %#v", d)
   707  	client := meta.(*govmomi.Client)
   708  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   709  	if err != nil {
   710  		return err
   711  	}
   712  	finder := find.NewFinder(client.Client, true)
   713  	finder = finder.SetDatacenter(dc)
   714  
   715  	vm, err := finder.VirtualMachine(context.TODO(), d.Id())
   716  	if err != nil {
   717  		d.SetId("")
   718  		return nil
   719  	}
   720  
   721  	var mvm mo.VirtualMachine
   722  
   723  	// wait for interfaces to appear
   724  	_, err = vm.WaitForNetIP(context.TODO(), true)
   725  	if err != nil {
   726  		return err
   727  	}
   728  
   729  	collector := property.DefaultCollector(client.Client)
   730  	if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore"}, &mvm); err != nil {
   731  		return err
   732  	}
   733  
   734  	log.Printf("[DEBUG] %#v", dc)
   735  	log.Printf("[DEBUG] %#v", mvm.Summary.Config)
   736  	log.Printf("[DEBUG] %#v", mvm.Guest.Net)
   737  
   738  	networkInterfaces := make([]map[string]interface{}, 0)
   739  	for _, v := range mvm.Guest.Net {
   740  		if v.DeviceConfigId >= 0 {
   741  			log.Printf("[DEBUG] %#v", v.Network)
   742  			networkInterface := make(map[string]interface{})
   743  			networkInterface["label"] = v.Network
   744  			for _, ip := range v.IpConfig.IpAddress {
   745  				p := net.ParseIP(ip.IpAddress)
   746  				if p.To4() != nil {
   747  					log.Printf("[DEBUG] %#v", p.String())
   748  					log.Printf("[DEBUG] %#v", ip.PrefixLength)
   749  					networkInterface["ipv4_address"] = p.String()
   750  					networkInterface["ipv4_prefix_length"] = ip.PrefixLength
   751  				} else if p.To16() != nil {
   752  					log.Printf("[DEBUG] %#v", p.String())
   753  					log.Printf("[DEBUG] %#v", ip.PrefixLength)
   754  					networkInterface["ipv6_address"] = p.String()
   755  					networkInterface["ipv6_prefix_length"] = ip.PrefixLength
   756  				}
   757  				log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
   758  			}
   759  			log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
   760  			networkInterfaces = append(networkInterfaces, networkInterface)
   761  		}
   762  	}
   763  	if mvm.Guest.IpStack != nil {
   764  		for _, v := range mvm.Guest.IpStack {
   765  			if v.IpRouteConfig != nil && v.IpRouteConfig.IpRoute != nil {
   766  				for _, route := range v.IpRouteConfig.IpRoute {
   767  					if route.Gateway.Device != "" {
   768  						gatewaySetting := ""
   769  						if route.Network == "::" {
   770  							gatewaySetting = "ipv6_gateway"
   771  						} else if route.Network == "0.0.0.0" {
   772  							gatewaySetting = "ipv4_gateway"
   773  						}
   774  						if gatewaySetting != "" {
   775  							deviceID, err := strconv.Atoi(route.Gateway.Device)
   776  							if err != nil {
   777  								log.Printf("[WARN] error at processing %s of device id %#v: %#v", gatewaySetting, route.Gateway.Device, err)
   778  							} else {
   779  								log.Printf("[DEBUG] %s of device id %d: %s", gatewaySetting, deviceID, route.Gateway.IpAddress)
   780  								networkInterfaces[deviceID][gatewaySetting] = route.Gateway.IpAddress
   781  							}
   782  						}
   783  					}
   784  				}
   785  			}
   786  		}
   787  	}
   788  	log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces)
   789  	err = d.Set("network_interface", networkInterfaces)
   790  	if err != nil {
   791  		return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces)
   792  	}
   793  
   794  	log.Printf("[DEBUG] ip address: %v", networkInterfaces[0]["ipv4_address"].(string))
   795  	d.SetConnInfo(map[string]string{
   796  		"type": "ssh",
   797  		"host": networkInterfaces[0]["ipv4_address"].(string),
   798  	})
   799  
   800  	var rootDatastore string
   801  	for _, v := range mvm.Datastore {
   802  		var md mo.Datastore
   803  		if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil {
   804  			return err
   805  		}
   806  		if md.Parent.Type == "StoragePod" {
   807  			var msp mo.StoragePod
   808  			if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil {
   809  				return err
   810  			}
   811  			rootDatastore = msp.Name
   812  			log.Printf("[DEBUG] %#v", msp.Name)
   813  		} else {
   814  			rootDatastore = md.Name
   815  			log.Printf("[DEBUG] %#v", md.Name)
   816  		}
   817  		break
   818  	}
   819  
   820  	d.Set("datacenter", dc)
   821  	d.Set("memory", mvm.Summary.Config.MemorySizeMB)
   822  	d.Set("memory_reservation", mvm.Summary.Config.MemoryReservation)
   823  	d.Set("cpu", mvm.Summary.Config.NumCpu)
   824  	d.Set("datastore", rootDatastore)
   825  
   826  	return nil
   827  }
   828  
   829  func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error {
   830  	client := meta.(*govmomi.Client)
   831  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   832  	if err != nil {
   833  		return err
   834  	}
   835  	finder := find.NewFinder(client.Client, true)
   836  	finder = finder.SetDatacenter(dc)
   837  
   838  	vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string)))
   839  	if err != nil {
   840  		return err
   841  	}
   842  
   843  	log.Printf("[INFO] Deleting virtual machine: %s", d.Id())
   844  	state, err := vm.PowerState(context.TODO())
   845  	if err != nil {
   846  		return err
   847  	}
   848  
   849  	if state == types.VirtualMachinePowerStatePoweredOn {
   850  		task, err := vm.PowerOff(context.TODO())
   851  		if err != nil {
   852  			return err
   853  		}
   854  
   855  		err = task.Wait(context.TODO())
   856  		if err != nil {
   857  			return err
   858  		}
   859  	}
   860  
   861  	task, err := vm.Destroy(context.TODO())
   862  	if err != nil {
   863  		return err
   864  	}
   865  
   866  	err = task.Wait(context.TODO())
   867  	if err != nil {
   868  		return err
   869  	}
   870  
   871  	d.SetId("")
   872  	return nil
   873  }
   874  
   875  // addHardDisk adds a new Hard Disk to the VirtualMachine.
   876  func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string, datastore *object.Datastore, diskPath string) error {
   877  	devices, err := vm.Device(context.TODO())
   878  	if err != nil {
   879  		return err
   880  	}
   881  	log.Printf("[DEBUG] vm devices: %#v\n", devices)
   882  
   883  	controller, err := devices.FindDiskController("scsi")
   884  	if err != nil {
   885  		return err
   886  	}
   887  	log.Printf("[DEBUG] disk controller: %#v\n", controller)
   888  
   889  	disk := devices.CreateDisk(controller, datastore.Reference(), diskPath)
   890  	existing := devices.SelectByBackingInfo(disk.Backing)
   891  	log.Printf("[DEBUG] disk: %#v\n", disk)
   892  
   893  	if len(existing) == 0 {
   894  		disk.CapacityInKB = int64(size * 1024 * 1024)
   895  		if iops != 0 {
   896  			disk.StorageIOAllocation = &types.StorageIOAllocationInfo{
   897  				Limit: iops,
   898  			}
   899  		}
   900  		backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
   901  
   902  		if diskType == "eager_zeroed" {
   903  			// eager zeroed thick virtual disk
   904  			backing.ThinProvisioned = types.NewBool(false)
   905  			backing.EagerlyScrub = types.NewBool(true)
   906  		} else if diskType == "thin" {
   907  			// thin provisioned virtual disk
   908  			backing.ThinProvisioned = types.NewBool(true)
   909  		}
   910  
   911  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk)
   912  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk.CapacityInKB)
   913  
   914  		return vm.AddDevice(context.TODO(), disk)
   915  	} else {
   916  		log.Printf("[DEBUG] addHardDisk: Disk already present.\n")
   917  
   918  		return nil
   919  	}
   920  }
   921  
   922  // addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path.
   923  func addCdrom(vm *object.VirtualMachine, datastore, path string) error {
   924  	devices, err := vm.Device(context.TODO())
   925  	if err != nil {
   926  		return err
   927  	}
   928  	log.Printf("[DEBUG] vm devices: %#v", devices)
   929  
   930  	controller, err := devices.FindIDEController("")
   931  	if err != nil {
   932  		return err
   933  	}
   934  	log.Printf("[DEBUG] ide controller: %#v", controller)
   935  
   936  	c, err := devices.CreateCdrom(controller)
   937  	if err != nil {
   938  		return err
   939  	}
   940  
   941  	c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path))
   942  	log.Printf("[DEBUG] addCdrom: %#v", c)
   943  
   944  	return vm.AddDevice(context.TODO(), c)
   945  }
   946  
   947  // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device.
   948  func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) {
   949  	network, err := f.Network(context.TODO(), "*"+label)
   950  	if err != nil {
   951  		return nil, err
   952  	}
   953  
   954  	backing, err := network.EthernetCardBackingInfo(context.TODO())
   955  	if err != nil {
   956  		return nil, err
   957  	}
   958  
   959  	if adapterType == "vmxnet3" {
   960  		return &types.VirtualDeviceConfigSpec{
   961  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
   962  			Device: &types.VirtualVmxnet3{
   963  				VirtualVmxnet: types.VirtualVmxnet{
   964  					VirtualEthernetCard: types.VirtualEthernetCard{
   965  						VirtualDevice: types.VirtualDevice{
   966  							Key:     -1,
   967  							Backing: backing,
   968  						},
   969  						AddressType: string(types.VirtualEthernetCardMacTypeGenerated),
   970  					},
   971  				},
   972  			},
   973  		}, nil
   974  	} else if adapterType == "e1000" {
   975  		return &types.VirtualDeviceConfigSpec{
   976  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
   977  			Device: &types.VirtualE1000{
   978  				VirtualEthernetCard: types.VirtualEthernetCard{
   979  					VirtualDevice: types.VirtualDevice{
   980  						Key:     -1,
   981  						Backing: backing,
   982  					},
   983  					AddressType: string(types.VirtualEthernetCardMacTypeGenerated),
   984  				},
   985  			},
   986  		}, nil
   987  	} else {
   988  		return nil, fmt.Errorf("Invalid network adapter type.")
   989  	}
   990  }
   991  
   992  // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine.
   993  func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) {
   994  	var key int32
   995  	var moveType string
   996  	if linkedClone {
   997  		moveType = "createNewChildDiskBacking"
   998  	} else {
   999  		moveType = "moveAllDiskBackingsAndDisallowSharing"
  1000  	}
  1001  	log.Printf("[DEBUG] relocate type: [%s]", moveType)
  1002  
  1003  	devices, err := vm.Device(context.TODO())
  1004  	if err != nil {
  1005  		return types.VirtualMachineRelocateSpec{}, err
  1006  	}
  1007  	for _, d := range devices {
  1008  		if devices.Type(d) == "disk" {
  1009  			key = int32(d.GetVirtualDevice().Key)
  1010  		}
  1011  	}
  1012  
  1013  	isThin := initType == "thin"
  1014  	rpr := rp.Reference()
  1015  	dsr := ds.Reference()
  1016  	return types.VirtualMachineRelocateSpec{
  1017  		Datastore:    &dsr,
  1018  		Pool:         &rpr,
  1019  		DiskMoveType: moveType,
  1020  		Disk: []types.VirtualMachineRelocateSpecDiskLocator{
  1021  			types.VirtualMachineRelocateSpecDiskLocator{
  1022  				Datastore: dsr,
  1023  				DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
  1024  					DiskMode:        "persistent",
  1025  					ThinProvisioned: types.NewBool(isThin),
  1026  					EagerlyScrub:    types.NewBool(!isThin),
  1027  				},
  1028  				DiskId: key,
  1029  			},
  1030  		},
  1031  	}, nil
  1032  }
  1033  
  1034  // getDatastoreObject gets datastore object.
  1035  func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) {
  1036  	s := object.NewSearchIndex(client.Client)
  1037  	ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name)
  1038  	if err != nil {
  1039  		return types.ManagedObjectReference{}, err
  1040  	}
  1041  	if ref == nil {
  1042  		return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name)
  1043  	}
  1044  	log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref)
  1045  	return ref.Reference(), nil
  1046  }
  1047  
  1048  // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action.
  1049  func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec {
  1050  	vmfr := f.VmFolder.Reference()
  1051  	rpr := rp.Reference()
  1052  	spr := storagePod.Reference()
  1053  
  1054  	sps := types.StoragePlacementSpec{
  1055  		Type:       "create",
  1056  		ConfigSpec: &configSpec,
  1057  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
  1058  			StoragePod: &spr,
  1059  		},
  1060  		Folder:       &vmfr,
  1061  		ResourcePool: &rpr,
  1062  	}
  1063  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
  1064  	return sps
  1065  }
  1066  
  1067  // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action.
  1068  func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec {
  1069  	vmr := vm.Reference()
  1070  	vmfr := f.VmFolder.Reference()
  1071  	rpr := rp.Reference()
  1072  	spr := storagePod.Reference()
  1073  
  1074  	var o mo.VirtualMachine
  1075  	err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o)
  1076  	if err != nil {
  1077  		return types.StoragePlacementSpec{}
  1078  	}
  1079  	ds := object.NewDatastore(c.Client, o.Datastore[0])
  1080  	log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds)
  1081  
  1082  	devices, err := vm.Device(context.TODO())
  1083  	if err != nil {
  1084  		return types.StoragePlacementSpec{}
  1085  	}
  1086  
  1087  	var key int32
  1088  	for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) {
  1089  		key = int32(d.GetVirtualDevice().Key)
  1090  		log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice())
  1091  	}
  1092  
  1093  	sps := types.StoragePlacementSpec{
  1094  		Type: "clone",
  1095  		Vm:   &vmr,
  1096  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
  1097  			StoragePod: &spr,
  1098  		},
  1099  		CloneSpec: &types.VirtualMachineCloneSpec{
  1100  			Location: types.VirtualMachineRelocateSpec{
  1101  				Disk: []types.VirtualMachineRelocateSpecDiskLocator{
  1102  					types.VirtualMachineRelocateSpecDiskLocator{
  1103  						Datastore:       ds.Reference(),
  1104  						DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{},
  1105  						DiskId:          key,
  1106  					},
  1107  				},
  1108  				Pool: &rpr,
  1109  			},
  1110  			PowerOn:  false,
  1111  			Template: false,
  1112  		},
  1113  		CloneName: "dummy",
  1114  		Folder:    &vmfr,
  1115  	}
  1116  	return sps
  1117  }
  1118  
  1119  // findDatastore finds Datastore object.
  1120  func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) {
  1121  	var datastore *object.Datastore
  1122  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
  1123  
  1124  	srm := object.NewStorageResourceManager(c.Client)
  1125  	rds, err := srm.RecommendDatastores(context.TODO(), sps)
  1126  	if err != nil {
  1127  		return nil, err
  1128  	}
  1129  	log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds)
  1130  
  1131  	spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction)
  1132  	datastore = object.NewDatastore(c.Client, spa.Destination)
  1133  	log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore)
  1134  
  1135  	return datastore, nil
  1136  }
  1137  
  1138  // createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller.
  1139  func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error {
  1140  	log.Printf("[DEBUG] add cdroms: %v", cdroms)
  1141  	for _, cd := range cdroms {
  1142  		log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore)
  1143  		log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path)
  1144  		err := addCdrom(vm, cd.datastore, cd.path)
  1145  		if err != nil {
  1146  			return err
  1147  		}
  1148  	}
  1149  
  1150  	return nil
  1151  }
  1152  
  1153  func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error {
  1154  	dc, err := getDatacenter(c, vm.datacenter)
  1155  
  1156  	if err != nil {
  1157  		return err
  1158  	}
  1159  	finder := find.NewFinder(c.Client, true)
  1160  	finder = finder.SetDatacenter(dc)
  1161  
  1162  	var template *object.VirtualMachine
  1163  	var template_mo mo.VirtualMachine
  1164  	if vm.template != "" {
  1165  		template, err = finder.VirtualMachine(context.TODO(), vm.template)
  1166  		if err != nil {
  1167  			return err
  1168  		}
  1169  		log.Printf("[DEBUG] template: %#v", template)
  1170  
  1171  		err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo)
  1172  		if err != nil {
  1173  			return err
  1174  		}
  1175  	}
  1176  
  1177  	var resourcePool *object.ResourcePool
  1178  	if vm.resourcePool == "" {
  1179  		if vm.cluster == "" {
  1180  			resourcePool, err = finder.DefaultResourcePool(context.TODO())
  1181  			if err != nil {
  1182  				return err
  1183  			}
  1184  		} else {
  1185  			resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources")
  1186  			if err != nil {
  1187  				return err
  1188  			}
  1189  		}
  1190  	} else {
  1191  		resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool)
  1192  		if err != nil {
  1193  			return err
  1194  		}
  1195  	}
  1196  	log.Printf("[DEBUG] resource pool: %#v", resourcePool)
  1197  
  1198  	dcFolders, err := dc.Folders(context.TODO())
  1199  	if err != nil {
  1200  		return err
  1201  	}
  1202  	log.Printf("[DEBUG] folder: %#v", vm.folder)
  1203  
  1204  	folder := dcFolders.VmFolder
  1205  	if len(vm.folder) > 0 {
  1206  		si := object.NewSearchIndex(c.Client)
  1207  		folderRef, err := si.FindByInventoryPath(
  1208  			context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder))
  1209  		if err != nil {
  1210  			return fmt.Errorf("Error reading folder %s: %s", vm.folder, err)
  1211  		} else if folderRef == nil {
  1212  			return fmt.Errorf("Cannot find folder %s", vm.folder)
  1213  		} else {
  1214  			folder = folderRef.(*object.Folder)
  1215  		}
  1216  	}
  1217  
  1218  	// make config spec
  1219  	configSpec := types.VirtualMachineConfigSpec{
  1220  		Name:              vm.name,
  1221  		NumCPUs:           vm.vcpu,
  1222  		NumCoresPerSocket: 1,
  1223  		MemoryMB:          vm.memoryMb,
  1224  		MemoryAllocation: &types.ResourceAllocationInfo{
  1225  			Reservation: vm.memoryAllocation.reservation,
  1226  		},
  1227  	}
  1228  	if vm.template == "" {
  1229  		configSpec.GuestId = "otherLinux64Guest"
  1230  	}
  1231  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1232  
  1233  	// make ExtraConfig
  1234  	log.Printf("[DEBUG] virtual machine Extra Config spec start")
  1235  	if len(vm.customConfigurations) > 0 {
  1236  		var ov []types.BaseOptionValue
  1237  		for k, v := range vm.customConfigurations {
  1238  			key := k
  1239  			value := v
  1240  			o := types.OptionValue{
  1241  				Key:   key,
  1242  				Value: &value,
  1243  			}
  1244  			log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v)
  1245  			ov = append(ov, &o)
  1246  		}
  1247  		configSpec.ExtraConfig = ov
  1248  		log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
  1249  	}
  1250  
  1251  	var datastore *object.Datastore
  1252  	if vm.datastore == "" {
  1253  		datastore, err = finder.DefaultDatastore(context.TODO())
  1254  		if err != nil {
  1255  			return err
  1256  		}
  1257  	} else {
  1258  		datastore, err = finder.Datastore(context.TODO(), vm.datastore)
  1259  		if err != nil {
  1260  			// TODO: datastore cluster support in govmomi finder function
  1261  			d, err := getDatastoreObject(c, dcFolders, vm.datastore)
  1262  			if err != nil {
  1263  				return err
  1264  			}
  1265  
  1266  			if d.Type == "StoragePod" {
  1267  				sp := object.StoragePod{
  1268  					Folder: object.NewFolder(c.Client, d),
  1269  				}
  1270  
  1271  				var sps types.StoragePlacementSpec
  1272  				if vm.template != "" {
  1273  					sps = buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp)
  1274  				} else {
  1275  					sps = buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec)
  1276  				}
  1277  
  1278  				datastore, err = findDatastore(c, sps)
  1279  				if err != nil {
  1280  					return err
  1281  				}
  1282  			} else {
  1283  				datastore = object.NewDatastore(c.Client, d)
  1284  			}
  1285  		}
  1286  	}
  1287  
  1288  	log.Printf("[DEBUG] datastore: %#v", datastore)
  1289  
  1290  	// network
  1291  	networkDevices := []types.BaseVirtualDeviceConfigSpec{}
  1292  	networkConfigs := []types.CustomizationAdapterMapping{}
  1293  	for _, network := range vm.networkInterfaces {
  1294  		// network device
  1295  		var networkDeviceType string
  1296  		if vm.template == "" {
  1297  			networkDeviceType = "e1000"
  1298  		} else {
  1299  			networkDeviceType = "vmxnet3"
  1300  		}
  1301  		nd, err := buildNetworkDevice(finder, network.label, networkDeviceType)
  1302  		if err != nil {
  1303  			return err
  1304  		}
  1305  		networkDevices = append(networkDevices, nd)
  1306  
  1307  		if vm.template != "" {
  1308  			var ipSetting types.CustomizationIPSettings
  1309  			if network.ipv4Address == "" {
  1310  				ipSetting.Ip = &types.CustomizationDhcpIpGenerator{}
  1311  			} else {
  1312  				if network.ipv4PrefixLength == 0 {
  1313  					return fmt.Errorf("Error: ipv4_prefix_length argument is empty.")
  1314  				}
  1315  				m := net.CIDRMask(network.ipv4PrefixLength, 32)
  1316  				sm := net.IPv4(m[0], m[1], m[2], m[3])
  1317  				subnetMask := sm.String()
  1318  				log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway)
  1319  				log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address)
  1320  				log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength)
  1321  				log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask)
  1322  				ipSetting.Gateway = []string{
  1323  					network.ipv4Gateway,
  1324  				}
  1325  				ipSetting.Ip = &types.CustomizationFixedIp{
  1326  					IpAddress: network.ipv4Address,
  1327  				}
  1328  				ipSetting.SubnetMask = subnetMask
  1329  			}
  1330  
  1331  			ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{}
  1332  			if network.ipv6Address == "" {
  1333  				ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
  1334  					&types.CustomizationDhcpIpV6Generator{},
  1335  				}
  1336  			} else {
  1337  				log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway)
  1338  				log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address)
  1339  				log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength)
  1340  
  1341  				ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
  1342  					&types.CustomizationFixedIpV6{
  1343  						IpAddress:  network.ipv6Address,
  1344  						SubnetMask: int32(network.ipv6PrefixLength),
  1345  					},
  1346  				}
  1347  				ipv6Spec.Gateway = []string{network.ipv6Gateway}
  1348  			}
  1349  			ipSetting.IpV6Spec = ipv6Spec
  1350  
  1351  			// network config
  1352  			config := types.CustomizationAdapterMapping{
  1353  				Adapter: ipSetting,
  1354  			}
  1355  			networkConfigs = append(networkConfigs, config)
  1356  		}
  1357  	}
  1358  	log.Printf("[DEBUG] network devices: %v", networkDevices)
  1359  	log.Printf("[DEBUG] network configs: %v", networkConfigs)
  1360  
  1361  	var task *object.Task
  1362  	if vm.template == "" {
  1363  		var mds mo.Datastore
  1364  		if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil {
  1365  			return err
  1366  		}
  1367  		log.Printf("[DEBUG] datastore: %#v", mds.Name)
  1368  		scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi")
  1369  		if err != nil {
  1370  			log.Printf("[ERROR] %s", err)
  1371  		}
  1372  
  1373  		configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{
  1374  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
  1375  			Device:    scsi,
  1376  		})
  1377  
  1378  		configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)}
  1379  
  1380  		task, err = folder.CreateVM(context.TODO(), configSpec, resourcePool, nil)
  1381  		if err != nil {
  1382  			log.Printf("[ERROR] %s", err)
  1383  		}
  1384  
  1385  		err = task.Wait(context.TODO())
  1386  		if err != nil {
  1387  			log.Printf("[ERROR] %s", err)
  1388  		}
  1389  
  1390  	} else {
  1391  
  1392  		relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType)
  1393  		if err != nil {
  1394  			return err
  1395  		}
  1396  
  1397  		log.Printf("[DEBUG] relocate spec: %v", relocateSpec)
  1398  
  1399  		// make vm clone spec
  1400  		cloneSpec := types.VirtualMachineCloneSpec{
  1401  			Location: relocateSpec,
  1402  			Template: false,
  1403  			Config:   &configSpec,
  1404  			PowerOn:  false,
  1405  		}
  1406  		if vm.linkedClone {
  1407  			if template_mo.Snapshot == nil {
  1408  				return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots")
  1409  			}
  1410  			cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot
  1411  		}
  1412  		log.Printf("[DEBUG] clone spec: %v", cloneSpec)
  1413  
  1414  		task, err = template.Clone(context.TODO(), folder, vm.name, cloneSpec)
  1415  		if err != nil {
  1416  			return err
  1417  		}
  1418  	}
  1419  
  1420  	err = task.Wait(context.TODO())
  1421  	if err != nil {
  1422  		log.Printf("[ERROR] %s", err)
  1423  	}
  1424  
  1425  	newVM, err := finder.VirtualMachine(context.TODO(), vm.Path())
  1426  	if err != nil {
  1427  		return err
  1428  	}
  1429  	log.Printf("[DEBUG] new vm: %v", newVM)
  1430  
  1431  	devices, err := newVM.Device(context.TODO())
  1432  	if err != nil {
  1433  		log.Printf("[DEBUG] Template devices can't be found")
  1434  		return err
  1435  	}
  1436  
  1437  	for _, dvc := range devices {
  1438  		// Issue 3559/3560: Delete all ethernet devices to add the correct ones later
  1439  		if devices.Type(dvc) == "ethernet" {
  1440  			err := newVM.RemoveDevice(context.TODO(), false, dvc)
  1441  			if err != nil {
  1442  				return err
  1443  			}
  1444  		}
  1445  	}
  1446  	// Add Network devices
  1447  	for _, dvc := range networkDevices {
  1448  		err := newVM.AddDevice(
  1449  			context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device)
  1450  		if err != nil {
  1451  			return err
  1452  		}
  1453  	}
  1454  
  1455  	// Create the cdroms if needed.
  1456  	if err := createCdroms(newVM, vm.cdroms); err != nil {
  1457  		return err
  1458  	}
  1459  
  1460  	firstDisk := 0
  1461  	if vm.template != "" {
  1462  		firstDisk++
  1463  	}
  1464  	for i := firstDisk; i < len(vm.hardDisks); i++ {
  1465  		log.Printf("[DEBUG] disk index: %v", i)
  1466  		err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, vm.hardDisks[i].vmdkPath)
  1467  		if err != nil {
  1468  			return err
  1469  		}
  1470  	}
  1471  
  1472  	if vm.skipCustomization || vm.template == "" {
  1473  		log.Printf("[DEBUG] VM customization skipped")
  1474  	} else {
  1475  		var identity_options types.BaseCustomizationIdentitySettings
  1476  		if strings.HasPrefix(template_mo.Config.GuestId, "win") {
  1477  			var timeZone int
  1478  			if vm.timeZone == "Etc/UTC" {
  1479  				vm.timeZone = "085"
  1480  			}
  1481  			timeZone, err := strconv.Atoi(vm.timeZone)
  1482  			if err != nil {
  1483  				return fmt.Errorf("Error converting TimeZone: %s", err)
  1484  			}
  1485  
  1486  			guiUnattended := types.CustomizationGuiUnattended{
  1487  				AutoLogon:      false,
  1488  				AutoLogonCount: 1,
  1489  				TimeZone:       int32(timeZone),
  1490  			}
  1491  
  1492  			customIdentification := types.CustomizationIdentification{}
  1493  
  1494  			userData := types.CustomizationUserData{
  1495  				ComputerName: &types.CustomizationFixedName{
  1496  					Name: strings.Split(vm.name, ".")[0],
  1497  				},
  1498  				ProductId: vm.windowsOptionalConfig.productKey,
  1499  				FullName:  "terraform",
  1500  				OrgName:   "terraform",
  1501  			}
  1502  
  1503  			if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" {
  1504  				customIdentification.DomainAdminPassword = &types.CustomizationPassword{
  1505  					PlainText: true,
  1506  					Value:     vm.windowsOptionalConfig.domainUserPassword,
  1507  				}
  1508  				customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser
  1509  				customIdentification.JoinDomain = vm.windowsOptionalConfig.domain
  1510  			}
  1511  
  1512  			if vm.windowsOptionalConfig.adminPassword != "" {
  1513  				guiUnattended.Password = &types.CustomizationPassword{
  1514  					PlainText: true,
  1515  					Value:     vm.windowsOptionalConfig.adminPassword,
  1516  				}
  1517  			}
  1518  
  1519  			identity_options = &types.CustomizationSysprep{
  1520  				GuiUnattended:  guiUnattended,
  1521  				Identification: customIdentification,
  1522  				UserData:       userData,
  1523  			}
  1524  		} else {
  1525  			identity_options = &types.CustomizationLinuxPrep{
  1526  				HostName: &types.CustomizationFixedName{
  1527  					Name: strings.Split(vm.name, ".")[0],
  1528  				},
  1529  				Domain:     vm.domain,
  1530  				TimeZone:   vm.timeZone,
  1531  				HwClockUTC: types.NewBool(true),
  1532  			}
  1533  		}
  1534  
  1535  		// create CustomizationSpec
  1536  		customSpec := types.CustomizationSpec{
  1537  			Identity: identity_options,
  1538  			GlobalIPSettings: types.CustomizationGlobalIPSettings{
  1539  				DnsSuffixList: vm.dnsSuffixes,
  1540  				DnsServerList: vm.dnsServers,
  1541  			},
  1542  			NicSettingMap: networkConfigs,
  1543  		}
  1544  		log.Printf("[DEBUG] custom spec: %v", customSpec)
  1545  
  1546  		log.Printf("[DEBUG] VM customization starting")
  1547  		taskb, err := newVM.Customize(context.TODO(), customSpec)
  1548  		if err != nil {
  1549  			return err
  1550  		}
  1551  		_, err = taskb.WaitForResult(context.TODO(), nil)
  1552  		if err != nil {
  1553  			return err
  1554  		}
  1555  		log.Printf("[DEBUG] VM customization finished")
  1556  	}
  1557  
  1558  	if vm.bootableVmdk || vm.template != "" {
  1559  		newVM.PowerOn(context.TODO())
  1560  	}
  1561  	return nil
  1562  }