github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/builtin/providers/vsphere/resource_vsphere_virtual_machine.go (about)

     1  package vsphere
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"github.com/vmware/govmomi"
    13  	"github.com/vmware/govmomi/find"
    14  	"github.com/vmware/govmomi/object"
    15  	"github.com/vmware/govmomi/property"
    16  	"github.com/vmware/govmomi/vim25/mo"
    17  	"github.com/vmware/govmomi/vim25/types"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  var DefaultDNSSuffixes = []string{
    22  	"vsphere.local",
    23  }
    24  
    25  var DefaultDNSServers = []string{
    26  	"8.8.8.8",
    27  	"8.8.4.4",
    28  }
    29  
    30  type networkInterface struct {
    31  	deviceName       string
    32  	label            string
    33  	ipv4Address      string
    34  	ipv4PrefixLength int
    35  	ipv6Address      string
    36  	ipv6PrefixLength int
    37  	adapterType      string // TODO: Make "adapter_type" argument
    38  }
    39  
    40  type hardDisk struct {
    41  	size     int64
    42  	iops     int64
    43  	initType string
    44  }
    45  
    46  type virtualMachine struct {
    47  	name                 string
    48  	folder               string
    49  	datacenter           string
    50  	cluster              string
    51  	resourcePool         string
    52  	datastore            string
    53  	vcpu                 int
    54  	memoryMb             int64
    55  	template             string
    56  	networkInterfaces    []networkInterface
    57  	hardDisks            []hardDisk
    58  	gateway              string
    59  	domain               string
    60  	timeZone             string
    61  	dnsSuffixes          []string
    62  	dnsServers           []string
    63  	customConfigurations map[string](types.AnyType)
    64  }
    65  
    66  func (v virtualMachine) Path() string {
    67  	return vmPath(v.folder, v.name)
    68  }
    69  
    70  func vmPath(folder string, name string) string {
    71  	var path string
    72  	if len(folder) > 0 {
    73  		path += folder + "/"
    74  	}
    75  	return path + name
    76  }
    77  
    78  func resourceVSphereVirtualMachine() *schema.Resource {
    79  	return &schema.Resource{
    80  		Create: resourceVSphereVirtualMachineCreate,
    81  		Read:   resourceVSphereVirtualMachineRead,
    82  		Delete: resourceVSphereVirtualMachineDelete,
    83  
    84  		Schema: map[string]*schema.Schema{
    85  			"name": &schema.Schema{
    86  				Type:     schema.TypeString,
    87  				Required: true,
    88  				ForceNew: true,
    89  			},
    90  
    91  			"folder": &schema.Schema{
    92  				Type:     schema.TypeString,
    93  				Optional: true,
    94  				ForceNew: true,
    95  			},
    96  
    97  			"vcpu": &schema.Schema{
    98  				Type:     schema.TypeInt,
    99  				Required: true,
   100  				ForceNew: true,
   101  			},
   102  
   103  			"memory": &schema.Schema{
   104  				Type:     schema.TypeInt,
   105  				Required: true,
   106  				ForceNew: true,
   107  			},
   108  
   109  			"datacenter": &schema.Schema{
   110  				Type:     schema.TypeString,
   111  				Optional: true,
   112  				ForceNew: true,
   113  			},
   114  
   115  			"cluster": &schema.Schema{
   116  				Type:     schema.TypeString,
   117  				Optional: true,
   118  				ForceNew: true,
   119  			},
   120  
   121  			"resource_pool": &schema.Schema{
   122  				Type:     schema.TypeString,
   123  				Optional: true,
   124  				ForceNew: true,
   125  			},
   126  
   127  			"gateway": &schema.Schema{
   128  				Type:     schema.TypeString,
   129  				Optional: true,
   130  				ForceNew: true,
   131  			},
   132  
   133  			"domain": &schema.Schema{
   134  				Type:     schema.TypeString,
   135  				Optional: true,
   136  				ForceNew: true,
   137  				Default:  "vsphere.local",
   138  			},
   139  
   140  			"time_zone": &schema.Schema{
   141  				Type:     schema.TypeString,
   142  				Optional: true,
   143  				ForceNew: true,
   144  				Default:  "Etc/UTC",
   145  			},
   146  
   147  			"dns_suffixes": &schema.Schema{
   148  				Type:     schema.TypeList,
   149  				Optional: true,
   150  				Elem:     &schema.Schema{Type: schema.TypeString},
   151  				ForceNew: true,
   152  			},
   153  
   154  			"dns_servers": &schema.Schema{
   155  				Type:     schema.TypeList,
   156  				Optional: true,
   157  				Elem:     &schema.Schema{Type: schema.TypeString},
   158  				ForceNew: true,
   159  			},
   160  
   161  			"custom_configuration_parameters": &schema.Schema{
   162  				Type:     schema.TypeMap,
   163  				Optional: true,
   164  				ForceNew: true,
   165  			},
   166  
   167  			"network_interface": &schema.Schema{
   168  				Type:     schema.TypeList,
   169  				Required: true,
   170  				ForceNew: true,
   171  				Elem: &schema.Resource{
   172  					Schema: map[string]*schema.Schema{
   173  						"label": &schema.Schema{
   174  							Type:     schema.TypeString,
   175  							Required: true,
   176  							ForceNew: true,
   177  						},
   178  
   179  						"ip_address": &schema.Schema{
   180  							Type:       schema.TypeString,
   181  							Optional:   true,
   182  							Computed:   true,
   183  							Deprecated: "Please use ipv4_address",
   184  						},
   185  
   186  						"subnet_mask": &schema.Schema{
   187  							Type:       schema.TypeString,
   188  							Optional:   true,
   189  							Computed:   true,
   190  							Deprecated: "Please use ipv4_prefix_length",
   191  						},
   192  
   193  						"ipv4_address": &schema.Schema{
   194  							Type:     schema.TypeString,
   195  							Optional: true,
   196  							Computed: true,
   197  						},
   198  
   199  						"ipv4_prefix_length": &schema.Schema{
   200  							Type:     schema.TypeInt,
   201  							Optional: true,
   202  							Computed: true,
   203  						},
   204  
   205  						// TODO: Imprement ipv6 parameters to be optional
   206  						"ipv6_address": &schema.Schema{
   207  							Type:     schema.TypeString,
   208  							Computed: true,
   209  							ForceNew: true,
   210  						},
   211  
   212  						"ipv6_prefix_length": &schema.Schema{
   213  							Type:     schema.TypeInt,
   214  							Computed: true,
   215  							ForceNew: true,
   216  						},
   217  
   218  						"adapter_type": &schema.Schema{
   219  							Type:     schema.TypeString,
   220  							Optional: true,
   221  							ForceNew: true,
   222  						},
   223  					},
   224  				},
   225  			},
   226  
   227  			"disk": &schema.Schema{
   228  				Type:     schema.TypeList,
   229  				Required: true,
   230  				ForceNew: true,
   231  				Elem: &schema.Resource{
   232  					Schema: map[string]*schema.Schema{
   233  						"template": &schema.Schema{
   234  							Type:     schema.TypeString,
   235  							Optional: true,
   236  							ForceNew: true,
   237  						},
   238  
   239  						"type": &schema.Schema{
   240  							Type:     schema.TypeString,
   241  							Optional: true,
   242  							ForceNew: true,
   243  							Default:  "eager_zeroed",
   244  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   245  								value := v.(string)
   246  								if value != "thin" && value != "eager_zeroed" {
   247  									errors = append(errors, fmt.Errorf(
   248  										"only 'thin' and 'eager_zeroed' are supported values for 'type'"))
   249  								}
   250  								return
   251  							},
   252  						},
   253  
   254  						"datastore": &schema.Schema{
   255  							Type:     schema.TypeString,
   256  							Optional: true,
   257  							ForceNew: true,
   258  						},
   259  
   260  						"size": &schema.Schema{
   261  							Type:     schema.TypeInt,
   262  							Optional: true,
   263  							ForceNew: true,
   264  						},
   265  
   266  						"iops": &schema.Schema{
   267  							Type:     schema.TypeInt,
   268  							Optional: true,
   269  							ForceNew: true,
   270  						},
   271  					},
   272  				},
   273  			},
   274  
   275  			"boot_delay": &schema.Schema{
   276  				Type:     schema.TypeInt,
   277  				Optional: true,
   278  				ForceNew: true,
   279  			},
   280  		},
   281  	}
   282  }
   283  
   284  func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error {
   285  	client := meta.(*govmomi.Client)
   286  
   287  	vm := virtualMachine{
   288  		name:     d.Get("name").(string),
   289  		vcpu:     d.Get("vcpu").(int),
   290  		memoryMb: int64(d.Get("memory").(int)),
   291  	}
   292  
   293  	if v, ok := d.GetOk("folder"); ok {
   294  		vm.folder = v.(string)
   295  	}
   296  
   297  	if v, ok := d.GetOk("datacenter"); ok {
   298  		vm.datacenter = v.(string)
   299  	}
   300  
   301  	if v, ok := d.GetOk("cluster"); ok {
   302  		vm.cluster = v.(string)
   303  	}
   304  
   305  	if v, ok := d.GetOk("resource_pool"); ok {
   306  		vm.resourcePool = v.(string)
   307  	}
   308  
   309  	if v, ok := d.GetOk("gateway"); ok {
   310  		vm.gateway = v.(string)
   311  	}
   312  
   313  	if v, ok := d.GetOk("domain"); ok {
   314  		vm.domain = v.(string)
   315  	}
   316  
   317  	if v, ok := d.GetOk("time_zone"); ok {
   318  		vm.timeZone = v.(string)
   319  	}
   320  
   321  	if raw, ok := d.GetOk("dns_suffixes"); ok {
   322  		for _, v := range raw.([]interface{}) {
   323  			vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string))
   324  		}
   325  	} else {
   326  		vm.dnsSuffixes = DefaultDNSSuffixes
   327  	}
   328  
   329  	if raw, ok := d.GetOk("dns_servers"); ok {
   330  		for _, v := range raw.([]interface{}) {
   331  			vm.dnsServers = append(vm.dnsServers, v.(string))
   332  		}
   333  	} else {
   334  		vm.dnsServers = DefaultDNSServers
   335  	}
   336  
   337  	if vL, ok := d.GetOk("custom_configuration_parameters"); ok {
   338  		if custom_configs, ok := vL.(map[string]interface{}); ok {
   339  			custom := make(map[string]types.AnyType)
   340  			for k, v := range custom_configs {
   341  				custom[k] = v
   342  			}
   343  			vm.customConfigurations = custom
   344  			log.Printf("[DEBUG] custom_configuration_parameters init: %v", vm.customConfigurations)
   345  		}
   346  	}
   347  
   348  	if vL, ok := d.GetOk("network_interface"); ok {
   349  		networks := make([]networkInterface, len(vL.([]interface{})))
   350  		for i, v := range vL.([]interface{}) {
   351  			network := v.(map[string]interface{})
   352  			networks[i].label = network["label"].(string)
   353  			if v, ok := network["ip_address"].(string); ok && v != "" {
   354  				networks[i].ipv4Address = v
   355  			}
   356  			if v, ok := network["subnet_mask"].(string); ok && v != "" {
   357  				ip := net.ParseIP(v).To4()
   358  				if ip != nil {
   359  					mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3])
   360  					pl, _ := mask.Size()
   361  					networks[i].ipv4PrefixLength = pl
   362  				} else {
   363  					return fmt.Errorf("subnet_mask parameter is invalid.")
   364  				}
   365  			}
   366  			if v, ok := network["ipv4_address"].(string); ok && v != "" {
   367  				networks[i].ipv4Address = v
   368  			}
   369  			if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 {
   370  				networks[i].ipv4PrefixLength = v
   371  			}
   372  		}
   373  		vm.networkInterfaces = networks
   374  		log.Printf("[DEBUG] network_interface init: %v", networks)
   375  	}
   376  
   377  	if vL, ok := d.GetOk("disk"); ok {
   378  		disks := make([]hardDisk, len(vL.([]interface{})))
   379  		for i, v := range vL.([]interface{}) {
   380  			disk := v.(map[string]interface{})
   381  			if i == 0 {
   382  				if v, ok := disk["template"].(string); ok && v != "" {
   383  					vm.template = v
   384  				} else {
   385  					if v, ok := disk["size"].(int); ok && v != 0 {
   386  						disks[i].size = int64(v)
   387  					} else {
   388  						return fmt.Errorf("If template argument is not specified, size argument is required.")
   389  					}
   390  				}
   391  				if v, ok := disk["datastore"].(string); ok && v != "" {
   392  					vm.datastore = v
   393  				}
   394  			} else {
   395  				if v, ok := disk["size"].(int); ok && v != 0 {
   396  					disks[i].size = int64(v)
   397  				} else {
   398  					return fmt.Errorf("Size argument is required.")
   399  				}
   400  
   401  			}
   402  			if v, ok := disk["iops"].(int); ok && v != 0 {
   403  				disks[i].iops = int64(v)
   404  			}
   405  			if v, ok := disk["type"].(string); ok && v != "" {
   406  				disks[i].initType = v
   407  			}
   408  		}
   409  		vm.hardDisks = disks
   410  		log.Printf("[DEBUG] disk init: %v", disks)
   411  	}
   412  
   413  	if vm.template != "" {
   414  		err := vm.deployVirtualMachine(client)
   415  		if err != nil {
   416  			return err
   417  		}
   418  	} else {
   419  		err := vm.createVirtualMachine(client)
   420  		if err != nil {
   421  			return err
   422  		}
   423  	}
   424  
   425  	if _, ok := d.GetOk("network_interface.0.ipv4_address"); !ok {
   426  		if v, ok := d.GetOk("boot_delay"); ok {
   427  			stateConf := &resource.StateChangeConf{
   428  				Pending:    []string{"pending"},
   429  				Target:     []string{"active"},
   430  				Refresh:    waitForNetworkingActive(client, vm.datacenter, vm.Path()),
   431  				Timeout:    600 * time.Second,
   432  				Delay:      time.Duration(v.(int)) * time.Second,
   433  				MinTimeout: 2 * time.Second,
   434  			}
   435  
   436  			_, err := stateConf.WaitForState()
   437  			if err != nil {
   438  				return err
   439  			}
   440  		}
   441  	}
   442  
   443  	if ip, ok := d.GetOk("network_interface.0.ipv4_address"); ok {
   444  		d.SetConnInfo(map[string]string{
   445  			"host": ip.(string),
   446  		})
   447  	} else {
   448  		log.Printf("[DEBUG] Could not get IP address for %s", d.Id())
   449  	}
   450  
   451  	d.SetId(vm.Path())
   452  	log.Printf("[INFO] Created virtual machine: %s", d.Id())
   453  
   454  	return resourceVSphereVirtualMachineRead(d, meta)
   455  }
   456  
   457  func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error {
   458  
   459  	log.Printf("[DEBUG] reading virtual machine: %#v", d)
   460  	client := meta.(*govmomi.Client)
   461  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   462  	if err != nil {
   463  		return err
   464  	}
   465  	finder := find.NewFinder(client.Client, true)
   466  	finder = finder.SetDatacenter(dc)
   467  
   468  	vm, err := finder.VirtualMachine(context.TODO(), d.Id())
   469  	if err != nil {
   470  		d.SetId("")
   471  		return nil
   472  	}
   473  
   474  	var mvm mo.VirtualMachine
   475  
   476  	collector := property.DefaultCollector(client.Client)
   477  	if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore"}, &mvm); err != nil {
   478  		return err
   479  	}
   480  
   481  	log.Printf("[DEBUG] %#v", dc)
   482  	log.Printf("[DEBUG] %#v", mvm.Summary.Config)
   483  	log.Printf("[DEBUG] %#v", mvm.Guest.Net)
   484  
   485  	networkInterfaces := make([]map[string]interface{}, 0)
   486  	for _, v := range mvm.Guest.Net {
   487  		if v.DeviceConfigId >= 0 {
   488  			log.Printf("[DEBUG] %#v", v.Network)
   489  			networkInterface := make(map[string]interface{})
   490  			networkInterface["label"] = v.Network
   491  			for _, ip := range v.IpConfig.IpAddress {
   492  				p := net.ParseIP(ip.IpAddress)
   493  				if p.To4() != nil {
   494  					log.Printf("[DEBUG] %#v", p.String())
   495  					log.Printf("[DEBUG] %#v", ip.PrefixLength)
   496  					networkInterface["ipv4_address"] = p.String()
   497  					networkInterface["ipv4_prefix_length"] = ip.PrefixLength
   498  				} else if p.To16() != nil {
   499  					log.Printf("[DEBUG] %#v", p.String())
   500  					log.Printf("[DEBUG] %#v", ip.PrefixLength)
   501  					networkInterface["ipv6_address"] = p.String()
   502  					networkInterface["ipv6_prefix_length"] = ip.PrefixLength
   503  				}
   504  				log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
   505  			}
   506  			log.Printf("[DEBUG] networkInterface: %#v", networkInterface)
   507  			networkInterfaces = append(networkInterfaces, networkInterface)
   508  		}
   509  	}
   510  	log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces)
   511  	err = d.Set("network_interface", networkInterfaces)
   512  	if err != nil {
   513  		return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces)
   514  	}
   515  
   516  	var rootDatastore string
   517  	for _, v := range mvm.Datastore {
   518  		var md mo.Datastore
   519  		if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil {
   520  			return err
   521  		}
   522  		if md.Parent.Type == "StoragePod" {
   523  			var msp mo.StoragePod
   524  			if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil {
   525  				return err
   526  			}
   527  			rootDatastore = msp.Name
   528  			log.Printf("[DEBUG] %#v", msp.Name)
   529  		} else {
   530  			rootDatastore = md.Name
   531  			log.Printf("[DEBUG] %#v", md.Name)
   532  		}
   533  		break
   534  	}
   535  
   536  	d.Set("datacenter", dc)
   537  	d.Set("memory", mvm.Summary.Config.MemorySizeMB)
   538  	d.Set("cpu", mvm.Summary.Config.NumCpu)
   539  	d.Set("datastore", rootDatastore)
   540  
   541  	return nil
   542  }
   543  
   544  func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error {
   545  	client := meta.(*govmomi.Client)
   546  	dc, err := getDatacenter(client, d.Get("datacenter").(string))
   547  	if err != nil {
   548  		return err
   549  	}
   550  	finder := find.NewFinder(client.Client, true)
   551  	finder = finder.SetDatacenter(dc)
   552  
   553  	vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string)))
   554  	if err != nil {
   555  		return err
   556  	}
   557  
   558  	log.Printf("[INFO] Deleting virtual machine: %s", d.Id())
   559  
   560  	task, err := vm.PowerOff(context.TODO())
   561  	if err != nil {
   562  		return err
   563  	}
   564  
   565  	err = task.Wait(context.TODO())
   566  	if err != nil {
   567  		return err
   568  	}
   569  
   570  	task, err = vm.Destroy(context.TODO())
   571  	if err != nil {
   572  		return err
   573  	}
   574  
   575  	err = task.Wait(context.TODO())
   576  	if err != nil {
   577  		return err
   578  	}
   579  
   580  	d.SetId("")
   581  	return nil
   582  }
   583  
   584  func waitForNetworkingActive(client *govmomi.Client, datacenter, name string) resource.StateRefreshFunc {
   585  	return func() (interface{}, string, error) {
   586  		dc, err := getDatacenter(client, datacenter)
   587  		if err != nil {
   588  			log.Printf("[ERROR] %#v", err)
   589  			return nil, "", err
   590  		}
   591  		finder := find.NewFinder(client.Client, true)
   592  		finder = finder.SetDatacenter(dc)
   593  
   594  		vm, err := finder.VirtualMachine(context.TODO(), name)
   595  		if err != nil {
   596  			log.Printf("[ERROR] %#v", err)
   597  			return nil, "", err
   598  		}
   599  
   600  		var mvm mo.VirtualMachine
   601  		collector := property.DefaultCollector(client.Client)
   602  		if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"summary"}, &mvm); err != nil {
   603  			log.Printf("[ERROR] %#v", err)
   604  			return nil, "", err
   605  		}
   606  
   607  		if mvm.Summary.Guest.IpAddress != "" {
   608  			log.Printf("[DEBUG] IP address with DHCP: %v", mvm.Summary.Guest.IpAddress)
   609  			return mvm.Summary, "active", err
   610  		} else {
   611  			log.Printf("[DEBUG] Waiting for IP address")
   612  			return nil, "pending", err
   613  		}
   614  	}
   615  }
   616  
   617  // addHardDisk adds a new Hard Disk to the VirtualMachine.
   618  func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string) error {
   619  	devices, err := vm.Device(context.TODO())
   620  	if err != nil {
   621  		return err
   622  	}
   623  	log.Printf("[DEBUG] vm devices: %#v\n", devices)
   624  
   625  	controller, err := devices.FindDiskController("scsi")
   626  	if err != nil {
   627  		return err
   628  	}
   629  	log.Printf("[DEBUG] disk controller: %#v\n", controller)
   630  
   631  	disk := devices.CreateDisk(controller, "")
   632  	existing := devices.SelectByBackingInfo(disk.Backing)
   633  	log.Printf("[DEBUG] disk: %#v\n", disk)
   634  
   635  	if len(existing) == 0 {
   636  		disk.CapacityInKB = int64(size * 1024 * 1024)
   637  		if iops != 0 {
   638  			disk.StorageIOAllocation = &types.StorageIOAllocationInfo{
   639  				Limit: iops,
   640  			}
   641  		}
   642  		backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
   643  
   644  		if diskType == "eager_zeroed" {
   645  			// eager zeroed thick virtual disk
   646  			backing.ThinProvisioned = types.NewBool(false)
   647  			backing.EagerlyScrub = types.NewBool(true)
   648  		} else if diskType == "thin" {
   649  			// thin provisioned virtual disk
   650  			backing.ThinProvisioned = types.NewBool(true)
   651  		}
   652  
   653  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk)
   654  		log.Printf("[DEBUG] addHardDisk: %#v\n", disk.CapacityInKB)
   655  
   656  		return vm.AddDevice(context.TODO(), disk)
   657  	} else {
   658  		log.Printf("[DEBUG] addHardDisk: Disk already present.\n")
   659  
   660  		return nil
   661  	}
   662  }
   663  
   664  // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device.
   665  func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) {
   666  	network, err := f.Network(context.TODO(), "*"+label)
   667  	if err != nil {
   668  		return nil, err
   669  	}
   670  
   671  	backing, err := network.EthernetCardBackingInfo(context.TODO())
   672  	if err != nil {
   673  		return nil, err
   674  	}
   675  
   676  	if adapterType == "vmxnet3" {
   677  		return &types.VirtualDeviceConfigSpec{
   678  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
   679  			Device: &types.VirtualVmxnet3{
   680  				VirtualVmxnet: types.VirtualVmxnet{
   681  					VirtualEthernetCard: types.VirtualEthernetCard{
   682  						VirtualDevice: types.VirtualDevice{
   683  							Key:     -1,
   684  							Backing: backing,
   685  						},
   686  						AddressType: string(types.VirtualEthernetCardMacTypeGenerated),
   687  					},
   688  				},
   689  			},
   690  		}, nil
   691  	} else if adapterType == "e1000" {
   692  		return &types.VirtualDeviceConfigSpec{
   693  			Operation: types.VirtualDeviceConfigSpecOperationAdd,
   694  			Device: &types.VirtualE1000{
   695  				VirtualEthernetCard: types.VirtualEthernetCard{
   696  					VirtualDevice: types.VirtualDevice{
   697  						Key:     -1,
   698  						Backing: backing,
   699  					},
   700  					AddressType: string(types.VirtualEthernetCardMacTypeGenerated),
   701  				},
   702  			},
   703  		}, nil
   704  	} else {
   705  		return nil, fmt.Errorf("Invalid network adapter type.")
   706  	}
   707  }
   708  
   709  // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine.
   710  func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, initType string) (types.VirtualMachineRelocateSpec, error) {
   711  	var key int
   712  
   713  	devices, err := vm.Device(context.TODO())
   714  	if err != nil {
   715  		return types.VirtualMachineRelocateSpec{}, err
   716  	}
   717  	for _, d := range devices {
   718  		if devices.Type(d) == "disk" {
   719  			key = d.GetVirtualDevice().Key
   720  		}
   721  	}
   722  
   723  	isThin := initType == "thin"
   724  	rpr := rp.Reference()
   725  	dsr := ds.Reference()
   726  	return types.VirtualMachineRelocateSpec{
   727  		Datastore: &dsr,
   728  		Pool:      &rpr,
   729  		Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   730  			types.VirtualMachineRelocateSpecDiskLocator{
   731  				Datastore: dsr,
   732  				DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   733  					DiskMode:        "persistent",
   734  					ThinProvisioned: types.NewBool(isThin),
   735  					EagerlyScrub:    types.NewBool(!isThin),
   736  				},
   737  				DiskId: key,
   738  			},
   739  		},
   740  	}, nil
   741  }
   742  
   743  // getDatastoreObject gets datastore object.
   744  func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) {
   745  	s := object.NewSearchIndex(client.Client)
   746  	ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name)
   747  	if err != nil {
   748  		return types.ManagedObjectReference{}, err
   749  	}
   750  	if ref == nil {
   751  		return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name)
   752  	}
   753  	log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref)
   754  	return ref.Reference(), nil
   755  }
   756  
   757  // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action.
   758  func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec {
   759  	vmfr := f.VmFolder.Reference()
   760  	rpr := rp.Reference()
   761  	spr := storagePod.Reference()
   762  
   763  	sps := types.StoragePlacementSpec{
   764  		Type:       "create",
   765  		ConfigSpec: &configSpec,
   766  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
   767  			StoragePod: &spr,
   768  		},
   769  		Folder:       &vmfr,
   770  		ResourcePool: &rpr,
   771  	}
   772  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
   773  	return sps
   774  }
   775  
   776  // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action.
   777  func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec {
   778  	vmr := vm.Reference()
   779  	vmfr := f.VmFolder.Reference()
   780  	rpr := rp.Reference()
   781  	spr := storagePod.Reference()
   782  
   783  	var o mo.VirtualMachine
   784  	err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o)
   785  	if err != nil {
   786  		return types.StoragePlacementSpec{}
   787  	}
   788  	ds := object.NewDatastore(c.Client, o.Datastore[0])
   789  	log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds)
   790  
   791  	devices, err := vm.Device(context.TODO())
   792  	if err != nil {
   793  		return types.StoragePlacementSpec{}
   794  	}
   795  
   796  	var key int
   797  	for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) {
   798  		key = d.GetVirtualDevice().Key
   799  		log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice())
   800  	}
   801  
   802  	sps := types.StoragePlacementSpec{
   803  		Type: "clone",
   804  		Vm:   &vmr,
   805  		PodSelectionSpec: types.StorageDrsPodSelectionSpec{
   806  			StoragePod: &spr,
   807  		},
   808  		CloneSpec: &types.VirtualMachineCloneSpec{
   809  			Location: types.VirtualMachineRelocateSpec{
   810  				Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   811  					types.VirtualMachineRelocateSpecDiskLocator{
   812  						Datastore:       ds.Reference(),
   813  						DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{},
   814  						DiskId:          key,
   815  					},
   816  				},
   817  				Pool: &rpr,
   818  			},
   819  			PowerOn:  false,
   820  			Template: false,
   821  		},
   822  		CloneName: "dummy",
   823  		Folder:    &vmfr,
   824  	}
   825  	return sps
   826  }
   827  
   828  // findDatastore finds Datastore object.
   829  func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) {
   830  	var datastore *object.Datastore
   831  	log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps)
   832  
   833  	srm := object.NewStorageResourceManager(c.Client)
   834  	rds, err := srm.RecommendDatastores(context.TODO(), sps)
   835  	if err != nil {
   836  		return nil, err
   837  	}
   838  	log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds)
   839  
   840  	spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction)
   841  	datastore = object.NewDatastore(c.Client, spa.Destination)
   842  	log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore)
   843  
   844  	return datastore, nil
   845  }
   846  
   847  // createVirtualMachine creates a new VirtualMachine.
   848  func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error {
   849  	dc, err := getDatacenter(c, vm.datacenter)
   850  
   851  	if err != nil {
   852  		return err
   853  	}
   854  	finder := find.NewFinder(c.Client, true)
   855  	finder = finder.SetDatacenter(dc)
   856  
   857  	var resourcePool *object.ResourcePool
   858  	if vm.resourcePool == "" {
   859  		if vm.cluster == "" {
   860  			resourcePool, err = finder.DefaultResourcePool(context.TODO())
   861  			if err != nil {
   862  				return err
   863  			}
   864  		} else {
   865  			resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources")
   866  			if err != nil {
   867  				return err
   868  			}
   869  		}
   870  	} else {
   871  		resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool)
   872  		if err != nil {
   873  			return err
   874  		}
   875  	}
   876  	log.Printf("[DEBUG] resource pool: %#v", resourcePool)
   877  
   878  	dcFolders, err := dc.Folders(context.TODO())
   879  	if err != nil {
   880  		return err
   881  	}
   882  
   883  	log.Printf("[DEBUG] folder: %#v", vm.folder)
   884  	folder := dcFolders.VmFolder
   885  	if len(vm.folder) > 0 {
   886  		si := object.NewSearchIndex(c.Client)
   887  		folderRef, err := si.FindByInventoryPath(
   888  			context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder))
   889  		if err != nil {
   890  			return fmt.Errorf("Error reading folder %s: %s", vm.folder, err)
   891  		} else if folderRef == nil {
   892  			return fmt.Errorf("Cannot find folder %s", vm.folder)
   893  		} else {
   894  			folder = folderRef.(*object.Folder)
   895  		}
   896  	}
   897  
   898  	// network
   899  	networkDevices := []types.BaseVirtualDeviceConfigSpec{}
   900  	for _, network := range vm.networkInterfaces {
   901  		// network device
   902  		nd, err := buildNetworkDevice(finder, network.label, "e1000")
   903  		if err != nil {
   904  			return err
   905  		}
   906  		networkDevices = append(networkDevices, nd)
   907  	}
   908  
   909  	// make config spec
   910  	configSpec := types.VirtualMachineConfigSpec{
   911  		GuestId:           "otherLinux64Guest",
   912  		Name:              vm.name,
   913  		NumCPUs:           vm.vcpu,
   914  		NumCoresPerSocket: 1,
   915  		MemoryMB:          vm.memoryMb,
   916  		DeviceChange:      networkDevices,
   917  	}
   918  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
   919  
   920  	// make ExtraConfig
   921  	log.Printf("[DEBUG] virtual machine Extra Config spec start")
   922  	if len(vm.customConfigurations) > 0 {
   923  		var ov []types.BaseOptionValue
   924  		for k, v := range vm.customConfigurations {
   925  			key := k
   926  			value := v
   927  			o := types.OptionValue{
   928  				Key:   key,
   929  				Value: &value,
   930  			}
   931  			log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v)
   932  			ov = append(ov, &o)
   933  		}
   934  		configSpec.ExtraConfig = ov
   935  		log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
   936  	}
   937  
   938  	var datastore *object.Datastore
   939  	if vm.datastore == "" {
   940  		datastore, err = finder.DefaultDatastore(context.TODO())
   941  		if err != nil {
   942  			return err
   943  		}
   944  	} else {
   945  		datastore, err = finder.Datastore(context.TODO(), vm.datastore)
   946  		if err != nil {
   947  			// TODO: datastore cluster support in govmomi finder function
   948  			d, err := getDatastoreObject(c, dcFolders, vm.datastore)
   949  			if err != nil {
   950  				return err
   951  			}
   952  
   953  			if d.Type == "StoragePod" {
   954  				sp := object.StoragePod{
   955  					Folder: object.NewFolder(c.Client, d),
   956  				}
   957  				sps := buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec)
   958  				datastore, err = findDatastore(c, sps)
   959  				if err != nil {
   960  					return err
   961  				}
   962  			} else {
   963  				datastore = object.NewDatastore(c.Client, d)
   964  			}
   965  		}
   966  	}
   967  
   968  	log.Printf("[DEBUG] datastore: %#v", datastore)
   969  
   970  	var mds mo.Datastore
   971  	if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil {
   972  		return err
   973  	}
   974  	log.Printf("[DEBUG] datastore: %#v", mds.Name)
   975  	scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi")
   976  	if err != nil {
   977  		log.Printf("[ERROR] %s", err)
   978  	}
   979  
   980  	configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{
   981  		Operation: types.VirtualDeviceConfigSpecOperationAdd,
   982  		Device:    scsi,
   983  	})
   984  	configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)}
   985  
   986  	task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil)
   987  	if err != nil {
   988  		log.Printf("[ERROR] %s", err)
   989  	}
   990  
   991  	err = task.Wait(context.TODO())
   992  	if err != nil {
   993  		log.Printf("[ERROR] %s", err)
   994  	}
   995  
   996  	newVM, err := finder.VirtualMachine(context.TODO(), vm.Path())
   997  	if err != nil {
   998  		return err
   999  	}
  1000  	log.Printf("[DEBUG] new vm: %v", newVM)
  1001  
  1002  	log.Printf("[DEBUG] add hard disk: %v", vm.hardDisks)
  1003  	for _, hd := range vm.hardDisks {
  1004  		log.Printf("[DEBUG] add hard disk: %v", hd.size)
  1005  		log.Printf("[DEBUG] add hard disk: %v", hd.iops)
  1006  		err = addHardDisk(newVM, hd.size, hd.iops, "thin")
  1007  		if err != nil {
  1008  			return err
  1009  		}
  1010  	}
  1011  	return nil
  1012  }
  1013  
  1014  // deployVirtualMachine deploys a new VirtualMachine.
  1015  func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
  1016  	dc, err := getDatacenter(c, vm.datacenter)
  1017  	if err != nil {
  1018  		return err
  1019  	}
  1020  	finder := find.NewFinder(c.Client, true)
  1021  	finder = finder.SetDatacenter(dc)
  1022  
  1023  	template, err := finder.VirtualMachine(context.TODO(), vm.template)
  1024  	if err != nil {
  1025  		return err
  1026  	}
  1027  	log.Printf("[DEBUG] template: %#v", template)
  1028  
  1029  	var resourcePool *object.ResourcePool
  1030  	if vm.resourcePool == "" {
  1031  		if vm.cluster == "" {
  1032  			resourcePool, err = finder.DefaultResourcePool(context.TODO())
  1033  			if err != nil {
  1034  				return err
  1035  			}
  1036  		} else {
  1037  			resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources")
  1038  			if err != nil {
  1039  				return err
  1040  			}
  1041  		}
  1042  	} else {
  1043  		resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool)
  1044  		if err != nil {
  1045  			return err
  1046  		}
  1047  	}
  1048  	log.Printf("[DEBUG] resource pool: %#v", resourcePool)
  1049  
  1050  	dcFolders, err := dc.Folders(context.TODO())
  1051  	if err != nil {
  1052  		return err
  1053  	}
  1054  
  1055  	log.Printf("[DEBUG] folder: %#v", vm.folder)
  1056  	folder := dcFolders.VmFolder
  1057  	if len(vm.folder) > 0 {
  1058  		si := object.NewSearchIndex(c.Client)
  1059  		folderRef, err := si.FindByInventoryPath(
  1060  			context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder))
  1061  		if err != nil {
  1062  			return fmt.Errorf("Error reading folder %s: %s", vm.folder, err)
  1063  		} else if folderRef == nil {
  1064  			return fmt.Errorf("Cannot find folder %s", vm.folder)
  1065  		} else {
  1066  			folder = folderRef.(*object.Folder)
  1067  		}
  1068  	}
  1069  
  1070  	var datastore *object.Datastore
  1071  	if vm.datastore == "" {
  1072  		datastore, err = finder.DefaultDatastore(context.TODO())
  1073  		if err != nil {
  1074  			return err
  1075  		}
  1076  	} else {
  1077  		datastore, err = finder.Datastore(context.TODO(), vm.datastore)
  1078  		if err != nil {
  1079  			// TODO: datastore cluster support in govmomi finder function
  1080  			d, err := getDatastoreObject(c, dcFolders, vm.datastore)
  1081  			if err != nil {
  1082  				return err
  1083  			}
  1084  
  1085  			if d.Type == "StoragePod" {
  1086  				sp := object.StoragePod{
  1087  					Folder: object.NewFolder(c.Client, d),
  1088  				}
  1089  				sps := buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp)
  1090  
  1091  				datastore, err = findDatastore(c, sps)
  1092  				if err != nil {
  1093  					return err
  1094  				}
  1095  			} else {
  1096  				datastore = object.NewDatastore(c.Client, d)
  1097  			}
  1098  		}
  1099  	}
  1100  	log.Printf("[DEBUG] datastore: %#v", datastore)
  1101  
  1102  	relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.hardDisks[0].initType)
  1103  	if err != nil {
  1104  		return err
  1105  	}
  1106  
  1107  	log.Printf("[DEBUG] relocate spec: %v", relocateSpec)
  1108  
  1109  	// network
  1110  	networkDevices := []types.BaseVirtualDeviceConfigSpec{}
  1111  	networkConfigs := []types.CustomizationAdapterMapping{}
  1112  	for _, network := range vm.networkInterfaces {
  1113  		// network device
  1114  		nd, err := buildNetworkDevice(finder, network.label, "vmxnet3")
  1115  		if err != nil {
  1116  			return err
  1117  		}
  1118  		networkDevices = append(networkDevices, nd)
  1119  
  1120  		// TODO: IPv6 support
  1121  		var ipSetting types.CustomizationIPSettings
  1122  		if network.ipv4Address == "" {
  1123  			ipSetting = types.CustomizationIPSettings{
  1124  				Ip: &types.CustomizationDhcpIpGenerator{},
  1125  			}
  1126  		} else {
  1127  			if network.ipv4PrefixLength == 0 {
  1128  				return fmt.Errorf("Error: ipv4_prefix_length argument is empty.")
  1129  			}
  1130  			m := net.CIDRMask(network.ipv4PrefixLength, 32)
  1131  			sm := net.IPv4(m[0], m[1], m[2], m[3])
  1132  			subnetMask := sm.String()
  1133  			log.Printf("[DEBUG] gateway: %v", vm.gateway)
  1134  			log.Printf("[DEBUG] ipv4 address: %v", network.ipv4Address)
  1135  			log.Printf("[DEBUG] ipv4 prefix length: %v", network.ipv4PrefixLength)
  1136  			log.Printf("[DEBUG] ipv4 subnet mask: %v", subnetMask)
  1137  			ipSetting = types.CustomizationIPSettings{
  1138  				Gateway: []string{
  1139  					vm.gateway,
  1140  				},
  1141  				Ip: &types.CustomizationFixedIp{
  1142  					IpAddress: network.ipv4Address,
  1143  				},
  1144  				SubnetMask: subnetMask,
  1145  			}
  1146  		}
  1147  
  1148  		// network config
  1149  		config := types.CustomizationAdapterMapping{
  1150  			Adapter: ipSetting,
  1151  		}
  1152  		networkConfigs = append(networkConfigs, config)
  1153  	}
  1154  	log.Printf("[DEBUG] network configs: %v", networkConfigs[0].Adapter)
  1155  
  1156  	// make config spec
  1157  	configSpec := types.VirtualMachineConfigSpec{
  1158  		NumCPUs:           vm.vcpu,
  1159  		NumCoresPerSocket: 1,
  1160  		MemoryMB:          vm.memoryMb,
  1161  	}
  1162  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1163  
  1164  	log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations)
  1165  
  1166  	// make ExtraConfig
  1167  	if len(vm.customConfigurations) > 0 {
  1168  		var ov []types.BaseOptionValue
  1169  		for k, v := range vm.customConfigurations {
  1170  			key := k
  1171  			value := v
  1172  			o := types.OptionValue{
  1173  				Key:   key,
  1174  				Value: &value,
  1175  			}
  1176  			ov = append(ov, &o)
  1177  		}
  1178  		configSpec.ExtraConfig = ov
  1179  		log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
  1180  	}
  1181  
  1182  	// create CustomizationSpec
  1183  	customSpec := types.CustomizationSpec{
  1184  		Identity: &types.CustomizationLinuxPrep{
  1185  			HostName: &types.CustomizationFixedName{
  1186  				Name: strings.Split(vm.name, ".")[0],
  1187  			},
  1188  			Domain:     vm.domain,
  1189  			TimeZone:   vm.timeZone,
  1190  			HwClockUTC: types.NewBool(true),
  1191  		},
  1192  		GlobalIPSettings: types.CustomizationGlobalIPSettings{
  1193  			DnsSuffixList: vm.dnsSuffixes,
  1194  			DnsServerList: vm.dnsServers,
  1195  		},
  1196  		NicSettingMap: networkConfigs,
  1197  	}
  1198  	log.Printf("[DEBUG] custom spec: %v", customSpec)
  1199  
  1200  	// make vm clone spec
  1201  	cloneSpec := types.VirtualMachineCloneSpec{
  1202  		Location: relocateSpec,
  1203  		Template: false,
  1204  		Config:   &configSpec,
  1205  		PowerOn:  false,
  1206  	}
  1207  	log.Printf("[DEBUG] clone spec: %v", cloneSpec)
  1208  
  1209  	task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec)
  1210  	if err != nil {
  1211  		return err
  1212  	}
  1213  
  1214  	_, err = task.WaitForResult(context.TODO(), nil)
  1215  	if err != nil {
  1216  		return err
  1217  	}
  1218  
  1219  	newVM, err := finder.VirtualMachine(context.TODO(), vm.Path())
  1220  	if err != nil {
  1221  		return err
  1222  	}
  1223  	log.Printf("[DEBUG] new vm: %v", newVM)
  1224  
  1225  	devices, err := newVM.Device(context.TODO())
  1226  	if err != nil {
  1227  		log.Printf("[DEBUG] Template devices can't be found")
  1228  		return err
  1229  	}
  1230  
  1231  	for _, dvc := range devices {
  1232  		// Issue 3559/3560: Delete all ethernet devices to add the correct ones later
  1233  		if devices.Type(dvc) == "ethernet" {
  1234  			err := newVM.RemoveDevice(context.TODO(), dvc)
  1235  			if err != nil {
  1236  				return err
  1237  			}
  1238  		}
  1239  	}
  1240  	// Add Network devices
  1241  	for _, dvc := range networkDevices {
  1242  		err := newVM.AddDevice(
  1243  			context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device)
  1244  		if err != nil {
  1245  			return err
  1246  		}
  1247  	}
  1248  
  1249  	taskb, err := newVM.Customize(context.TODO(), customSpec)
  1250  	if err != nil {
  1251  		return err
  1252  	}
  1253  
  1254  	_, err = taskb.WaitForResult(context.TODO(), nil)
  1255  	if err != nil {
  1256  		return err
  1257  	}
  1258  	log.Printf("[DEBUG]VM customization finished")
  1259  
  1260  	for i := 1; i < len(vm.hardDisks); i++ {
  1261  		err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType)
  1262  		if err != nil {
  1263  			return err
  1264  		}
  1265  	}
  1266  	log.Printf("[DEBUG] virtual machine config spec: %v", configSpec)
  1267  
  1268  	newVM.PowerOn(context.TODO())
  1269  
  1270  	ip, err := newVM.WaitForIP(context.TODO())
  1271  	if err != nil {
  1272  		return err
  1273  	}
  1274  	log.Printf("[DEBUG] ip address: %v", ip)
  1275  
  1276  	return nil
  1277  }