github.com/miquella/terraform@v0.6.17-0.20160517195040-40db82f25ec0/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  	if vm.template != "" {
   695  		err := vm.deployVirtualMachine(client)
   696  		if err != nil {
   697  			return err
   698  		}
   699  	} else {
   700  		err := vm.createVirtualMachine(client)
   701  		if err != nil {
   702  			return err
   703  		}
   704  	}
   705  
   706  	d.SetId(vm.Path())
   707  	log.Printf("[INFO] Created virtual machine: %s", d.Id())
   708  
   709  	return resourceVSphereVirtualMachineRead(d, meta)
   710  }
   711  
   712  func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error {
   713  	log.Printf("[DEBUG] reading virtual machine: %#v", d)
   714  	client := meta.(*govmomi.Client)
   715  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   716  	if err != nil {
   717  		return err
   718  	}
   719  	finder := find.NewFinder(client.Client, true)
   720  	finder = finder.SetDatacenter(dc)
   721  
   722  	vm, err := finder.VirtualMachine(context.TODO(), d.Id())
   723  	if err != nil {
   724  		d.SetId("")
   725  		return nil
   726  	}
   727  
   728  	var mvm mo.VirtualMachine
   729  
   730  	// wait for interfaces to appear
   731  	_, err = vm.WaitForNetIP(context.TODO(), true)
   732  	if err != nil {
   733  		return err
   734  	}
   735  
   736  	collector := property.DefaultCollector(client.Client)
   737  	if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore"}, &mvm); err != nil {
   738  		return err
   739  	}
   740  
   741  	log.Printf("[DEBUG] %#v", dc)
   742  	log.Printf("[DEBUG] %#v", mvm.Summary.Config)
   743  	log.Printf("[DEBUG] %#v", mvm.Guest.Net)
   744  
   745  	networkInterfaces := make([]map[string]interface{}, 0)
   746  	for _, v := range mvm.Guest.Net {
   747  		if v.DeviceConfigId >= 0 {
   748  			log.Printf("[DEBUG] %#v", v.Network)
   749  			networkInterface := make(map[string]interface{})
   750  			networkInterface["label"] = v.Network
   751  			for _, ip := range v.IpConfig.IpAddress {
   752  				p := net.ParseIP(ip.IpAddress)
   753  				if p.To4() != nil {
   754  					log.Printf("[DEBUG] %#v", p.String())
   755  					log.Printf("[DEBUG] %#v", ip.PrefixLength)
   756  					networkInterface["ipv4_address"] = p.String()
   757  					networkInterface["ipv4_prefix_length"] = ip.PrefixLength
   758  				} else if p.To16() != nil {
   759  					log.Printf("[DEBUG] %#v", p.String())
   760  					log.Printf("[DEBUG] %#v", ip.PrefixLength)
   761  					networkInterface["ipv6_address"] = p.String()
   762  					networkInterface["ipv6_prefix_length"] = ip.PrefixLength
   763  				}
   764  				log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
   765  			}
   766  			log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
   767  			networkInterfaces = append(networkInterfaces, networkInterface)
   768  		}
   769  	}
   770  	if mvm.Guest.IpStack != nil {
   771  		for _, v := range mvm.Guest.IpStack {
   772  			if v.IpRouteConfig != nil && v.IpRouteConfig.IpRoute != nil {
   773  				for _, route := range v.IpRouteConfig.IpRoute {
   774  					if route.Gateway.Device != "" {
   775  						gatewaySetting := ""
   776  						if route.Network == "::" {
   777  							gatewaySetting = "ipv6_gateway"
   778  						} else if route.Network == "0.0.0.0" {
   779  							gatewaySetting = "ipv4_gateway"
   780  						}
   781  						if gatewaySetting != "" {
   782  							deviceID, err := strconv.Atoi(route.Gateway.Device)
   783  							if err != nil {
   784  								log.Printf("[WARN] error at processing %s of device id %#v: %#v", gatewaySetting, route.Gateway.Device, err)
   785  							} else {
   786  								log.Printf("[DEBUG] %s of device id %d: %s", gatewaySetting, deviceID, route.Gateway.IpAddress)
   787  								networkInterfaces[deviceID][gatewaySetting] = route.Gateway.IpAddress
   788  							}
   789  						}
   790  					}
   791  				}
   792  			}
   793  		}
   794  	}
   795  	log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces)
   796  	err = d.Set("network_interface", networkInterfaces)
   797  	if err != nil {
   798  		return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces)
   799  	}
   800  
   801  	log.Printf("[DEBUG] ip address: %v", networkInterfaces[0]["ipv4_address"].(string))
   802  	d.SetConnInfo(map[string]string{
   803  		"type": "ssh",
   804  		"host": networkInterfaces[0]["ipv4_address"].(string),
   805  	})
   806  
   807  	var rootDatastore string
   808  	for _, v := range mvm.Datastore {
   809  		var md mo.Datastore
   810  		if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil {
   811  			return err
   812  		}
   813  		if md.Parent.Type == "StoragePod" {
   814  			var msp mo.StoragePod
   815  			if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil {
   816  				return err
   817  			}
   818  			rootDatastore = msp.Name
   819  			log.Printf("[DEBUG] %#v", msp.Name)
   820  		} else {
   821  			rootDatastore = md.Name
   822  			log.Printf("[DEBUG] %#v", md.Name)
   823  		}
   824  		break
   825  	}
   826  
   827  	d.Set("datacenter", dc)
   828  	d.Set("memory", mvm.Summary.Config.MemorySizeMB)
   829  	d.Set("memory_reservation", mvm.Summary.Config.MemoryReservation)
   830  	d.Set("cpu", mvm.Summary.Config.NumCpu)
   831  	d.Set("datastore", rootDatastore)
   832  
   833  	return nil
   834  }
   835  
   836  func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error {
   837  	client := meta.(*govmomi.Client)
   838  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   839  	if err != nil {
   840  		return err
   841  	}
   842  	finder := find.NewFinder(client.Client, true)
   843  	finder = finder.SetDatacenter(dc)
   844  
   845  	vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string)))
   846  	if err != nil {
   847  		return err
   848  	}
   849  
   850  	log.Printf("[INFO] Deleting virtual machine: %s", d.Id())
   851  	state, err := vm.PowerState(context.TODO())
   852  	if err != nil {
   853  		return err
   854  	}
   855  
   856  	if state == types.VirtualMachinePowerStatePoweredOn {
   857  		task, err := vm.PowerOff(context.TODO())
   858  		if err != nil {
   859  			return err
   860  		}
   861  
   862  		err = task.Wait(context.TODO())
   863  		if err != nil {
   864  			return err
   865  		}
   866  	}
   867  
   868  	task, err := vm.Destroy(context.TODO())
   869  	if err != nil {
   870  		return err
   871  	}
   872  
   873  	err = task.Wait(context.TODO())
   874  	if err != nil {
   875  		return err
   876  	}
   877  
   878  	d.SetId("")
   879  	return nil
   880  }
   881  
   882  // addHardDisk adds a new Hard Disk to the VirtualMachine.
   883  func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string, datastore *object.Datastore, diskPath string) error {
   884  	devices, err := vm.Device(context.TODO())
   885  	if err != nil {
   886  		return err
   887  	}
   888  	log.Printf("[DEBUG] vm devices: %#v\n", devices)
   889  
   890  	controller, err := devices.FindDiskController("scsi")
   891  	if err != nil {
   892  		return err
   893  	}
   894  	log.Printf("[DEBUG] disk controller: %#v\n", controller)
   895  
   896  	disk := devices.CreateDisk(controller, datastore.Reference(), diskPath)
   897  	existing := devices.SelectByBackingInfo(disk.Backing)
   898  	log.Printf("[DEBUG] disk: %#v\n", disk)
   899  
   900  	if len(existing) == 0 {
   901  		disk.CapacityInKB = int64(size * 1024 * 1024)
   902  		if iops != 0 {
   903  			disk.StorageIOAllocation = &types.StorageIOAllocationInfo{
   904  				Limit: iops,
   905  			}
   906  		}
   907  		backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
   908  
   909  		if diskType == "eager_zeroed" {
   910  			// eager zeroed thick virtual disk
   911  			backing.ThinProvisioned = types.NewBool(false)
   912  			backing.EagerlyScrub = types.NewBool(true)
   913  		} else if diskType == "thin" {
   914  			// thin provisioned virtual disk
   915  			backing.ThinProvisioned = types.NewBool(true)
   916  		}
   917  
   918  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk)
   919  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk.CapacityInKB)
   920  
   921  		return vm.AddDevice(context.TODO(), disk)
   922  	} else {
   923  		log.Printf("[DEBUG] addHardDisk: Disk already present.\n")
   924  
   925  		return nil
   926  	}
   927  }
   928  
   929  // addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path.
   930  func addCdrom(vm *object.VirtualMachine, datastore, path string) error {
   931  	devices, err := vm.Device(context.TODO())
   932  	if err != nil {
   933  		return err
   934  	}
   935  	log.Printf("[DEBUG] vm devices: %#v", devices)
   936  
   937  	controller, err := devices.FindIDEController("")
   938  	if err != nil {
   939  		return err
   940  	}
   941  	log.Printf("[DEBUG] ide controller: %#v", controller)
   942  
   943  	c, err := devices.CreateCdrom(controller)
   944  	if err != nil {
   945  		return err
   946  	}
   947  
   948  	c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path))
   949  	log.Printf("[DEBUG] addCdrom: %#v", c)
   950  
   951  	return vm.AddDevice(context.TODO(), c)
   952  }
   953  
   954  // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device.
   955  func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) {
   956  	network, err := f.Network(context.TODO(), "*"+label)
   957  	if err != nil {
   958  		return nil, err
   959  	}
   960  
   961  	backing, err := network.EthernetCardBackingInfo(context.TODO())
   962  	if err != nil {
   963  		return nil, err
   964  	}
   965  
   966  	if adapterType == "vmxnet3" {
   967  		return &types.VirtualDeviceConfigSpec{
   968  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
   969  			Device: &types.VirtualVmxnet3{
   970  				VirtualVmxnet: types.VirtualVmxnet{
   971  					VirtualEthernetCard: types.VirtualEthernetCard{
   972  						VirtualDevice: types.VirtualDevice{
   973  							Key:     -1,
   974  							Backing: backing,
   975  						},
   976  						AddressType: string(types.VirtualEthernetCardMacTypeGenerated),
   977  					},
   978  				},
   979  			},
   980  		}, nil
   981  	} else if adapterType == "e1000" {
   982  		return &types.VirtualDeviceConfigSpec{
   983  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
   984  			Device: &types.VirtualE1000{
   985  				VirtualEthernetCard: types.VirtualEthernetCard{
   986  					VirtualDevice: types.VirtualDevice{
   987  						Key:     -1,
   988  						Backing: backing,
   989  					},
   990  					AddressType: string(types.VirtualEthernetCardMacTypeGenerated),
   991  				},
   992  			},
   993  		}, nil
   994  	} else {
   995  		return nil, fmt.Errorf("Invalid network adapter type.")
   996  	}
   997  }
   998  
   999  // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine.
  1000  func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) {
  1001  	var key int32
  1002  	var moveType string
  1003  	if linkedClone {
  1004  		moveType = "createNewChildDiskBacking"
  1005  	} else {
  1006  		moveType = "moveAllDiskBackingsAndDisallowSharing"
  1007  	}
  1008  	log.Printf("[DEBUG] relocate type: [%s]", moveType)
  1009  
  1010  	devices, err := vm.Device(context.TODO())
  1011  	if err != nil {
  1012  		return types.VirtualMachineRelocateSpec{}, err
  1013  	}
  1014  	for _, d := range devices {
  1015  		if devices.Type(d) == "disk" {
  1016  			key = int32(d.GetVirtualDevice().Key)
  1017  		}
  1018  	}
  1019  
  1020  	isThin := initType == "thin"
  1021  	rpr := rp.Reference()
  1022  	dsr := ds.Reference()
  1023  	return types.VirtualMachineRelocateSpec{
  1024  		Datastore:    &dsr,
  1025  		Pool:         &rpr,
  1026  		DiskMoveType: moveType,
  1027  		Disk: []types.VirtualMachineRelocateSpecDiskLocator{
  1028  			types.VirtualMachineRelocateSpecDiskLocator{
  1029  				Datastore: dsr,
  1030  				DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
  1031  					DiskMode:        "persistent",
  1032  					ThinProvisioned: types.NewBool(isThin),
  1033  					EagerlyScrub:    types.NewBool(!isThin),
  1034  				},
  1035  				DiskId: key,
  1036  			},
  1037  		},
  1038  	}, nil
  1039  }
  1040  
  1041  // getDatastoreObject gets datastore object.
  1042  func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) {
  1043  	s := object.NewSearchIndex(client.Client)
  1044  	ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name)
  1045  	if err != nil {
  1046  		return types.ManagedObjectReference{}, err
  1047  	}
  1048  	if ref == nil {
  1049  		return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name)
  1050  	}
  1051  	log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref)
  1052  	return ref.Reference(), nil
  1053  }
  1054  
  1055  // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action.
  1056  func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec {
  1057  	vmfr := f.VmFolder.Reference()
  1058  	rpr := rp.Reference()
  1059  	spr := storagePod.Reference()
  1060  
  1061  	sps := types.StoragePlacementSpec{
  1062  		Type:       "create",
  1063  		ConfigSpec: &configSpec,
  1064  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
  1065  			StoragePod: &spr,
  1066  		},
  1067  		Folder:       &vmfr,
  1068  		ResourcePool: &rpr,
  1069  	}
  1070  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
  1071  	return sps
  1072  }
  1073  
  1074  // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action.
  1075  func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec {
  1076  	vmr := vm.Reference()
  1077  	vmfr := f.VmFolder.Reference()
  1078  	rpr := rp.Reference()
  1079  	spr := storagePod.Reference()
  1080  
  1081  	var o mo.VirtualMachine
  1082  	err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o)
  1083  	if err != nil {
  1084  		return types.StoragePlacementSpec{}
  1085  	}
  1086  	ds := object.NewDatastore(c.Client, o.Datastore[0])
  1087  	log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds)
  1088  
  1089  	devices, err := vm.Device(context.TODO())
  1090  	if err != nil {
  1091  		return types.StoragePlacementSpec{}
  1092  	}
  1093  
  1094  	var key int32
  1095  	for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) {
  1096  		key = int32(d.GetVirtualDevice().Key)
  1097  		log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice())
  1098  	}
  1099  
  1100  	sps := types.StoragePlacementSpec{
  1101  		Type: "clone",
  1102  		Vm:   &vmr,
  1103  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
  1104  			StoragePod: &spr,
  1105  		},
  1106  		CloneSpec: &types.VirtualMachineCloneSpec{
  1107  			Location: types.VirtualMachineRelocateSpec{
  1108  				Disk: []types.VirtualMachineRelocateSpecDiskLocator{
  1109  					types.VirtualMachineRelocateSpecDiskLocator{
  1110  						Datastore:       ds.Reference(),
  1111  						DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{},
  1112  						DiskId:          key,
  1113  					},
  1114  				},
  1115  				Pool: &rpr,
  1116  			},
  1117  			PowerOn:  false,
  1118  			Template: false,
  1119  		},
  1120  		CloneName: "dummy",
  1121  		Folder:    &vmfr,
  1122  	}
  1123  	return sps
  1124  }
  1125  
  1126  // findDatastore finds Datastore object.
  1127  func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) {
  1128  	var datastore *object.Datastore
  1129  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
  1130  
  1131  	srm := object.NewStorageResourceManager(c.Client)
  1132  	rds, err := srm.RecommendDatastores(context.TODO(), sps)
  1133  	if err != nil {
  1134  		return nil, err
  1135  	}
  1136  	log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds)
  1137  
  1138  	spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction)
  1139  	datastore = object.NewDatastore(c.Client, spa.Destination)
  1140  	log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore)
  1141  
  1142  	return datastore, nil
  1143  }
  1144  
  1145  // createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller.
  1146  func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error {
  1147  	log.Printf("[DEBUG] add cdroms: %v", cdroms)
  1148  	for _, cd := range cdroms {
  1149  		log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore)
  1150  		log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path)
  1151  		err := addCdrom(vm, cd.datastore, cd.path)
  1152  		if err != nil {
  1153  			return err
  1154  		}
  1155  	}
  1156  
  1157  	return nil
  1158  }
  1159  
  1160  // createVirtualMachine creates a new VirtualMachine.
  1161  func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
  1162  	dc, err := getDatacenter(c, vm.datacenter)
  1163  
  1164  	if err != nil {
  1165  		return err
  1166  	}
  1167  	finder := find.NewFinder(c.Client, true)
  1168  	finder = finder.SetDatacenter(dc)
  1169  
  1170  	var resourcePool *object.ResourcePool
  1171  	if vm.resourcePool == "" {
  1172  		if vm.cluster == "" {
  1173  			resourcePool, err = finder.DefaultResourcePool(context.TODO())
  1174  			if err != nil {
  1175  				return err
  1176  			}
  1177  		} else {
  1178  			resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources")
  1179  			if err != nil {
  1180  				return err
  1181  			}
  1182  		}
  1183  	} else {
  1184  		resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool)
  1185  		if err != nil {
  1186  			return err
  1187  		}
  1188  	}
  1189  	log.Printf("[DEBUG] resource pool: %#v", resourcePool)
  1190  
  1191  	dcFolders, err := dc.Folders(context.TODO())
  1192  	if err != nil {
  1193  		return err
  1194  	}
  1195  
  1196  	log.Printf("[DEBUG] folder: %#v", vm.folder)
  1197  	folder := dcFolders.VmFolder
  1198  	if len(vm.folder) > 0 {
  1199  		si := object.NewSearchIndex(c.Client)
  1200  		folderRef, err := si.FindByInventoryPath(
  1201  			context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder))
  1202  		if err != nil {
  1203  			return fmt.Errorf("Error reading folder %s: %s", vm.folder, err)
  1204  		} else if folderRef == nil {
  1205  			return fmt.Errorf("Cannot find folder %s", vm.folder)
  1206  		} else {
  1207  			folder = folderRef.(*object.Folder)
  1208  		}
  1209  	}
  1210  
  1211  	// network
  1212  	networkDevices := []types.BaseVirtualDeviceConfigSpec{}
  1213  	for _, network := range vm.networkInterfaces {
  1214  		// network device
  1215  		nd, err := buildNetworkDevice(finder, network.label, "e1000")
  1216  		if err != nil {
  1217  			return err
  1218  		}
  1219  		networkDevices = append(networkDevices, nd)
  1220  	}
  1221  
  1222  	// make config spec
  1223  	configSpec := types.VirtualMachineConfigSpec{
  1224  		GuestId:           "otherLinux64Guest",
  1225  		Name:              vm.name,
  1226  		NumCPUs:           vm.vcpu,
  1227  		NumCoresPerSocket: 1,
  1228  		MemoryMB:          vm.memoryMb,
  1229  		MemoryAllocation: &types.ResourceAllocationInfo{
  1230  			Reservation: vm.memoryAllocation.reservation,
  1231  		},
  1232  		DeviceChange: networkDevices,
  1233  	}
  1234  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1235  
  1236  	// make ExtraConfig
  1237  	log.Printf("[DEBUG] virtual machine Extra Config spec start")
  1238  	if len(vm.customConfigurations) > 0 {
  1239  		var ov []types.BaseOptionValue
  1240  		for k, v := range vm.customConfigurations {
  1241  			key := k
  1242  			value := v
  1243  			o := types.OptionValue{
  1244  				Key:   key,
  1245  				Value: &value,
  1246  			}
  1247  			log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v)
  1248  			ov = append(ov, &o)
  1249  		}
  1250  		configSpec.ExtraConfig = ov
  1251  		log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
  1252  	}
  1253  
  1254  	var datastore *object.Datastore
  1255  	if vm.datastore == "" {
  1256  		datastore, err = finder.DefaultDatastore(context.TODO())
  1257  		if err != nil {
  1258  			return err
  1259  		}
  1260  	} else {
  1261  		datastore, err = finder.Datastore(context.TODO(), vm.datastore)
  1262  		if err != nil {
  1263  			// TODO: datastore cluster support in govmomi finder function
  1264  			d, err := getDatastoreObject(c, dcFolders, vm.datastore)
  1265  			if err != nil {
  1266  				return err
  1267  			}
  1268  
  1269  			if d.Type == "StoragePod" {
  1270  				sp := object.StoragePod{
  1271  					Folder: object.NewFolder(c.Client, d),
  1272  				}
  1273  				sps := buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec)
  1274  				datastore, err = findDatastore(c, sps)
  1275  				if err != nil {
  1276  					return err
  1277  				}
  1278  			} else {
  1279  				datastore = object.NewDatastore(c.Client, d)
  1280  			}
  1281  		}
  1282  	}
  1283  
  1284  	log.Printf("[DEBUG] datastore: %#v", datastore)
  1285  
  1286  	var mds mo.Datastore
  1287  	if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil {
  1288  		return err
  1289  	}
  1290  	log.Printf("[DEBUG] datastore: %#v", mds.Name)
  1291  	scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi")
  1292  	if err != nil {
  1293  		log.Printf("[ERROR] %s", err)
  1294  	}
  1295  
  1296  	configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{
  1297  		Operation: types.VirtualDeviceConfigSpecOperationAdd,
  1298  		Device:    scsi,
  1299  	})
  1300  
  1301  	configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)}
  1302  
  1303  	task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil)
  1304  	if err != nil {
  1305  		log.Printf("[ERROR] %s", err)
  1306  	}
  1307  
  1308  	err = task.Wait(context.TODO())
  1309  	if err != nil {
  1310  		log.Printf("[ERROR] %s", err)
  1311  	}
  1312  
  1313  	newVM, err := finder.VirtualMachine(context.TODO(), vm.Path())
  1314  	if err != nil {
  1315  		return err
  1316  	}
  1317  	log.Printf("[DEBUG] new vm: %v", newVM)
  1318  
  1319  	log.Printf("[DEBUG] add hard disk: %v", vm.hardDisks)
  1320  	for _, hd := range vm.hardDisks {
  1321  		log.Printf("[DEBUG] add hard disk: %v", hd.size)
  1322  		log.Printf("[DEBUG] add hard disk: %v", hd.iops)
  1323  		err = addHardDisk(newVM, hd.size, hd.iops, "thin", datastore, hd.vmdkPath)
  1324  		if err != nil {
  1325  			return err
  1326  		}
  1327  	}
  1328  
  1329  	// Create the cdroms if needed.
  1330  	if err := createCdroms(newVM, vm.cdroms); err != nil {
  1331  		return err
  1332  	}
  1333  
  1334  	if vm.bootableVmdk {
  1335  		newVM.PowerOn(context.TODO())
  1336  		ip, err := newVM.WaitForIP(context.TODO())
  1337  		if err != nil {
  1338  			return err
  1339  		}
  1340  		log.Printf("[DEBUG] ip address: %v", ip)
  1341  	}
  1342  
  1343  	return nil
  1344  }
  1345  
  1346  // deployVirtualMachine deploys a new VirtualMachine.
  1347  func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
  1348  	dc, err := getDatacenter(c, vm.datacenter)
  1349  	if err != nil {
  1350  		return err
  1351  	}
  1352  	finder := find.NewFinder(c.Client, true)
  1353  	finder = finder.SetDatacenter(dc)
  1354  
  1355  	template, err := finder.VirtualMachine(context.TODO(), vm.template)
  1356  	if err != nil {
  1357  		return err
  1358  	}
  1359  	log.Printf("[DEBUG] template: %#v", template)
  1360  
  1361  	var resourcePool *object.ResourcePool
  1362  	if vm.resourcePool == "" {
  1363  		if vm.cluster == "" {
  1364  			resourcePool, err = finder.DefaultResourcePool(context.TODO())
  1365  			if err != nil {
  1366  				return err
  1367  			}
  1368  		} else {
  1369  			resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources")
  1370  			if err != nil {
  1371  				return err
  1372  			}
  1373  		}
  1374  	} else {
  1375  		resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool)
  1376  		if err != nil {
  1377  			return err
  1378  		}
  1379  	}
  1380  	log.Printf("[DEBUG] resource pool: %#v", resourcePool)
  1381  
  1382  	dcFolders, err := dc.Folders(context.TODO())
  1383  	if err != nil {
  1384  		return err
  1385  	}
  1386  
  1387  	log.Printf("[DEBUG] folder: %#v", vm.folder)
  1388  	folder := dcFolders.VmFolder
  1389  	if len(vm.folder) > 0 {
  1390  		si := object.NewSearchIndex(c.Client)
  1391  		folderRef, err := si.FindByInventoryPath(
  1392  			context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder))
  1393  		if err != nil {
  1394  			return fmt.Errorf("Error reading folder %s: %s", vm.folder, err)
  1395  		} else if folderRef == nil {
  1396  			return fmt.Errorf("Cannot find folder %s", vm.folder)
  1397  		} else {
  1398  			folder = folderRef.(*object.Folder)
  1399  		}
  1400  	}
  1401  
  1402  	var datastore *object.Datastore
  1403  	if vm.datastore == "" {
  1404  		datastore, err = finder.DefaultDatastore(context.TODO())
  1405  		if err != nil {
  1406  			return err
  1407  		}
  1408  	} else {
  1409  		datastore, err = finder.Datastore(context.TODO(), vm.datastore)
  1410  		if err != nil {
  1411  			// TODO: datastore cluster support in govmomi finder function
  1412  			d, err := getDatastoreObject(c, dcFolders, vm.datastore)
  1413  			if err != nil {
  1414  				return err
  1415  			}
  1416  
  1417  			if d.Type == "StoragePod" {
  1418  				sp := object.StoragePod{
  1419  					Folder: object.NewFolder(c.Client, d),
  1420  				}
  1421  				sps := buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp)
  1422  
  1423  				datastore, err = findDatastore(c, sps)
  1424  				if err != nil {
  1425  					return err
  1426  				}
  1427  			} else {
  1428  				datastore = object.NewDatastore(c.Client, d)
  1429  			}
  1430  		}
  1431  	}
  1432  	log.Printf("[DEBUG] datastore: %#v", datastore)
  1433  
  1434  	relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType)
  1435  	if err != nil {
  1436  		return err
  1437  	}
  1438  
  1439  	log.Printf("[DEBUG] relocate spec: %v", relocateSpec)
  1440  
  1441  	// network
  1442  	networkDevices := []types.BaseVirtualDeviceConfigSpec{}
  1443  	networkConfigs := []types.CustomizationAdapterMapping{}
  1444  	for _, network := range vm.networkInterfaces {
  1445  		// network device
  1446  		nd, err := buildNetworkDevice(finder, network.label, "vmxnet3")
  1447  		if err != nil {
  1448  			return err
  1449  		}
  1450  		networkDevices = append(networkDevices, nd)
  1451  
  1452  		var ipSetting types.CustomizationIPSettings
  1453  		if network.ipv4Address == "" {
  1454  			ipSetting.Ip = &types.CustomizationDhcpIpGenerator{}
  1455  		} else {
  1456  			if network.ipv4PrefixLength == 0 {
  1457  				return fmt.Errorf("Error: ipv4_prefix_length argument is empty.")
  1458  			}
  1459  			m := net.CIDRMask(network.ipv4PrefixLength, 32)
  1460  			sm := net.IPv4(m[0], m[1], m[2], m[3])
  1461  			subnetMask := sm.String()
  1462  			log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway)
  1463  			log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address)
  1464  			log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength)
  1465  			log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask)
  1466  			ipSetting.Gateway = []string{
  1467  				network.ipv4Gateway,
  1468  			}
  1469  			ipSetting.Ip = &types.CustomizationFixedIp{
  1470  				IpAddress: network.ipv4Address,
  1471  			}
  1472  			ipSetting.SubnetMask = subnetMask
  1473  		}
  1474  
  1475  		ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{}
  1476  		if network.ipv6Address == "" {
  1477  			ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
  1478  				&types.CustomizationDhcpIpV6Generator{},
  1479  			}
  1480  		} else {
  1481  			log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway)
  1482  			log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address)
  1483  			log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength)
  1484  
  1485  			ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
  1486  				&types.CustomizationFixedIpV6{
  1487  					IpAddress:  network.ipv6Address,
  1488  					SubnetMask: int32(network.ipv6PrefixLength),
  1489  				},
  1490  			}
  1491  			ipv6Spec.Gateway = []string{network.ipv6Gateway}
  1492  		}
  1493  		ipSetting.IpV6Spec = ipv6Spec
  1494  
  1495  		// network config
  1496  		config := types.CustomizationAdapterMapping{
  1497  			Adapter: ipSetting,
  1498  		}
  1499  		networkConfigs = append(networkConfigs, config)
  1500  	}
  1501  	log.Printf("[DEBUG] network configs: %v", networkConfigs[0].Adapter)
  1502  
  1503  	// make config spec
  1504  	configSpec := types.VirtualMachineConfigSpec{
  1505  		NumCPUs:           vm.vcpu,
  1506  		NumCoresPerSocket: 1,
  1507  		MemoryMB:          vm.memoryMb,
  1508  		MemoryAllocation: &types.ResourceAllocationInfo{
  1509  			Reservation: vm.memoryAllocation.reservation,
  1510  		},
  1511  	}
  1512  
  1513  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1514  
  1515  	log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations)
  1516  
  1517  	// make ExtraConfig
  1518  	if len(vm.customConfigurations) > 0 {
  1519  		var ov []types.BaseOptionValue
  1520  		for k, v := range vm.customConfigurations {
  1521  			key := k
  1522  			value := v
  1523  			o := types.OptionValue{
  1524  				Key:   key,
  1525  				Value: &value,
  1526  			}
  1527  			ov = append(ov, &o)
  1528  		}
  1529  		configSpec.ExtraConfig = ov
  1530  		log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
  1531  	}
  1532  
  1533  	var template_mo mo.VirtualMachine
  1534  	err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo)
  1535  
  1536  	var identity_options types.BaseCustomizationIdentitySettings
  1537  	if strings.HasPrefix(template_mo.Config.GuestId, "win") {
  1538  		var timeZone int
  1539  		if vm.timeZone == "Etc/UTC" {
  1540  			vm.timeZone = "085"
  1541  		}
  1542  		timeZone, err := strconv.Atoi(vm.timeZone)
  1543  		if err != nil {
  1544  			return fmt.Errorf("Error converting TimeZone: %s", err)
  1545  		}
  1546  
  1547  		guiUnattended := types.CustomizationGuiUnattended{
  1548  			AutoLogon:      false,
  1549  			AutoLogonCount: 1,
  1550  			TimeZone:       int32(timeZone),
  1551  		}
  1552  
  1553  		customIdentification := types.CustomizationIdentification{}
  1554  
  1555  		userData := types.CustomizationUserData{
  1556  			ComputerName: &types.CustomizationFixedName{
  1557  				Name: strings.Split(vm.name, ".")[0],
  1558  			},
  1559  			ProductId: vm.windowsOptionalConfig.productKey,
  1560  			FullName:  "terraform",
  1561  			OrgName:   "terraform",
  1562  		}
  1563  
  1564  		if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" {
  1565  			customIdentification.DomainAdminPassword = &types.CustomizationPassword{
  1566  				PlainText: true,
  1567  				Value:     vm.windowsOptionalConfig.domainUserPassword,
  1568  			}
  1569  			customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser
  1570  			customIdentification.JoinDomain = vm.windowsOptionalConfig.domain
  1571  		}
  1572  
  1573  		if vm.windowsOptionalConfig.adminPassword != "" {
  1574  			guiUnattended.Password = &types.CustomizationPassword{
  1575  				PlainText: true,
  1576  				Value:     vm.windowsOptionalConfig.adminPassword,
  1577  			}
  1578  		}
  1579  
  1580  		identity_options = &types.CustomizationSysprep{
  1581  			GuiUnattended:  guiUnattended,
  1582  			Identification: customIdentification,
  1583  			UserData:       userData,
  1584  		}
  1585  	} else {
  1586  		identity_options = &types.CustomizationLinuxPrep{
  1587  			HostName: &types.CustomizationFixedName{
  1588  				Name: strings.Split(vm.name, ".")[0],
  1589  			},
  1590  			Domain:     vm.domain,
  1591  			TimeZone:   vm.timeZone,
  1592  			HwClockUTC: types.NewBool(true),
  1593  		}
  1594  	}
  1595  
  1596  	// create CustomizationSpec
  1597  	customSpec := types.CustomizationSpec{
  1598  		Identity: identity_options,
  1599  		GlobalIPSettings: types.CustomizationGlobalIPSettings{
  1600  			DnsSuffixList: vm.dnsSuffixes,
  1601  			DnsServerList: vm.dnsServers,
  1602  		},
  1603  		NicSettingMap: networkConfigs,
  1604  	}
  1605  	log.Printf("[DEBUG] custom spec: %v", customSpec)
  1606  
  1607  	// make vm clone spec
  1608  	cloneSpec := types.VirtualMachineCloneSpec{
  1609  		Location: relocateSpec,
  1610  		Template: false,
  1611  		Config:   &configSpec,
  1612  		PowerOn:  false,
  1613  	}
  1614  	if vm.linkedClone {
  1615  		if err != nil {
  1616  			return fmt.Errorf("Error reading base VM properties: %s", err)
  1617  		}
  1618  		if template_mo.Snapshot == nil {
  1619  			return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots")
  1620  		}
  1621  		cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot
  1622  	}
  1623  	log.Printf("[DEBUG] clone spec: %v", cloneSpec)
  1624  
  1625  	task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec)
  1626  	if err != nil {
  1627  		return err
  1628  	}
  1629  
  1630  	_, err = task.WaitForResult(context.TODO(), nil)
  1631  	if err != nil {
  1632  		return err
  1633  	}
  1634  
  1635  	newVM, err := finder.VirtualMachine(context.TODO(), vm.Path())
  1636  	if err != nil {
  1637  		return err
  1638  	}
  1639  	log.Printf("[DEBUG] new vm: %v", newVM)
  1640  
  1641  	devices, err := newVM.Device(context.TODO())
  1642  	if err != nil {
  1643  		log.Printf("[DEBUG] Template devices can't be found")
  1644  		return err
  1645  	}
  1646  
  1647  	for _, dvc := range devices {
  1648  		// Issue 3559/3560: Delete all ethernet devices to add the correct ones later
  1649  		if devices.Type(dvc) == "ethernet" {
  1650  			err := newVM.RemoveDevice(context.TODO(), false, dvc)
  1651  			if err != nil {
  1652  				return err
  1653  			}
  1654  		}
  1655  	}
  1656  	// Add Network devices
  1657  	for _, dvc := range networkDevices {
  1658  		err := newVM.AddDevice(
  1659  			context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device)
  1660  		if err != nil {
  1661  			return err
  1662  		}
  1663  	}
  1664  
  1665  	// Create the cdroms if needed.
  1666  	if err := createCdroms(newVM, vm.cdroms); err != nil {
  1667  		return err
  1668  	}
  1669  
  1670  	if vm.skipCustomization {
  1671  		log.Printf("[DEBUG] VM customization skipped")
  1672  	} else {
  1673  		log.Printf("[DEBUG] VM customization starting")
  1674  		taskb, err := newVM.Customize(context.TODO(), customSpec)
  1675  		if err != nil {
  1676  			return err
  1677  		}
  1678  		_, err = taskb.WaitForResult(context.TODO(), nil)
  1679  		if err != nil {
  1680  			return err
  1681  		}
  1682  		log.Printf("[DEBUG] VM customization finished")
  1683  	}
  1684  
  1685  	for i := 1; i < len(vm.hardDisks); i++ {
  1686  		err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, vm.hardDisks[i].vmdkPath)
  1687  		if err != nil {
  1688  			return err
  1689  		}
  1690  	}
  1691  
  1692  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1693  
  1694  	newVM.PowerOn(context.TODO())
  1695  
  1696  	return nil
  1697  }