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