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 }