diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index ef4d70d30..513a354b5 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -691,16 +691,9 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ log.Printf("[DEBUG] cdrom init: %v", cdroms) } - if vm.template != "" { - err := vm.deployVirtualMachine(client) - if err != nil { - return err - } - } else { - err := vm.createVirtualMachine(client) - if err != nil { - return err - } + err := vm.setupVirtualMachine(client) + if err != nil { + return err } d.SetId(vm.Path()) @@ -1157,8 +1150,7 @@ func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error { return nil } -// createVirtualMachine creates a new VirtualMachine. -func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { +func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { dc, err := getDatacenter(c, vm.datacenter) if err != nil { @@ -1167,6 +1159,21 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { finder := find.NewFinder(c.Client, true) finder = finder.SetDatacenter(dc) + var template *object.VirtualMachine + var template_mo mo.VirtualMachine + if vm.template != "" { + template, err = finder.VirtualMachine(context.TODO(), vm.template) + if err != nil { + return err + } + log.Printf("[DEBUG] template: %#v", template) + + err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) + if err != nil { + return err + } + } + var resourcePool *object.ResourcePool if vm.resourcePool == "" { if vm.cluster == "" { @@ -1192,8 +1199,8 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { if err != nil { return err } - log.Printf("[DEBUG] folder: %#v", vm.folder) + folder := dcFolders.VmFolder if len(vm.folder) > 0 { si := object.NewSearchIndex(c.Client) @@ -1208,20 +1215,8 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { } } - // network - networkDevices := []types.BaseVirtualDeviceConfigSpec{} - for _, network := range vm.networkInterfaces { - // network device - nd, err := buildNetworkDevice(finder, network.label, "e1000") - if err != nil { - return err - } - networkDevices = append(networkDevices, nd) - } - // make config spec configSpec := types.VirtualMachineConfigSpec{ - GuestId: "otherLinux64Guest", Name: vm.name, NumCPUs: vm.vcpu, NumCoresPerSocket: 1, @@ -1229,7 +1224,9 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { MemoryAllocation: &types.ResourceAllocationInfo{ Reservation: vm.memoryAllocation.reservation, }, - DeviceChange: networkDevices, + } + if vm.template == "" { + configSpec.GuestId = "otherLinux64Guest" } log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) @@ -1270,7 +1267,14 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { sp := object.StoragePod{ Folder: object.NewFolder(c.Client, d), } - sps := buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) + + var sps types.StoragePlacementSpec + if vm.template != "" { + sps = buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) + } else { + sps = buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) + } + datastore, err = findDatastore(c, sps) if err != nil { return err @@ -1283,353 +1287,139 @@ func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { log.Printf("[DEBUG] datastore: %#v", datastore) - var mds mo.Datastore - if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil { - return err - } - log.Printf("[DEBUG] datastore: %#v", mds.Name) - scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi") - if err != nil { - log.Printf("[ERROR] %s", err) - } - - configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{ - Operation: types.VirtualDeviceConfigSpecOperationAdd, - Device: scsi, - }) - - configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} - - task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) - if err != nil { - log.Printf("[ERROR] %s", err) - } - - err = task.Wait(context.TODO()) - if err != nil { - log.Printf("[ERROR] %s", err) - } - - newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) - if err != nil { - return err - } - log.Printf("[DEBUG] new vm: %v", newVM) - - log.Printf("[DEBUG] add hard disk: %v", vm.hardDisks) - for _, hd := range vm.hardDisks { - log.Printf("[DEBUG] add hard disk: %v", hd.size) - log.Printf("[DEBUG] add hard disk: %v", hd.iops) - err = addHardDisk(newVM, hd.size, hd.iops, "thin", datastore, hd.vmdkPath) - if err != nil { - return err - } - } - - // Create the cdroms if needed. - if err := createCdroms(newVM, vm.cdroms); err != nil { - return err - } - - if vm.bootableVmdk { - newVM.PowerOn(context.TODO()) - ip, err := newVM.WaitForIP(context.TODO()) - if err != nil { - return err - } - log.Printf("[DEBUG] ip address: %v", ip) - } - - return nil -} - -// deployVirtualMachine deploys a new VirtualMachine. -func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { - dc, err := getDatacenter(c, vm.datacenter) - if err != nil { - return err - } - finder := find.NewFinder(c.Client, true) - finder = finder.SetDatacenter(dc) - - template, err := finder.VirtualMachine(context.TODO(), vm.template) - if err != nil { - return err - } - log.Printf("[DEBUG] template: %#v", template) - - var resourcePool *object.ResourcePool - if vm.resourcePool == "" { - if vm.cluster == "" { - resourcePool, err = finder.DefaultResourcePool(context.TODO()) - if err != nil { - return err - } - } else { - resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") - if err != nil { - return err - } - } - } else { - resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) - if err != nil { - return err - } - } - log.Printf("[DEBUG] resource pool: %#v", resourcePool) - - dcFolders, err := dc.Folders(context.TODO()) - if err != nil { - return err - } - - log.Printf("[DEBUG] folder: %#v", vm.folder) - folder := dcFolders.VmFolder - if len(vm.folder) > 0 { - si := object.NewSearchIndex(c.Client) - folderRef, err := si.FindByInventoryPath( - context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) - if err != nil { - return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) - } else if folderRef == nil { - return fmt.Errorf("Cannot find folder %s", vm.folder) - } else { - folder = folderRef.(*object.Folder) - } - } - - var datastore *object.Datastore - if vm.datastore == "" { - datastore, err = finder.DefaultDatastore(context.TODO()) - if err != nil { - return err - } - } else { - datastore, err = finder.Datastore(context.TODO(), vm.datastore) - if err != nil { - // TODO: datastore cluster support in govmomi finder function - d, err := getDatastoreObject(c, dcFolders, vm.datastore) - if err != nil { - return err - } - - if d.Type == "StoragePod" { - sp := object.StoragePod{ - Folder: object.NewFolder(c.Client, d), - } - sps := buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) - - datastore, err = findDatastore(c, sps) - if err != nil { - return err - } - } else { - datastore = object.NewDatastore(c.Client, d) - } - } - } - log.Printf("[DEBUG] datastore: %#v", datastore) - - relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType) - if err != nil { - return err - } - - log.Printf("[DEBUG] relocate spec: %v", relocateSpec) - // network networkDevices := []types.BaseVirtualDeviceConfigSpec{} networkConfigs := []types.CustomizationAdapterMapping{} for _, network := range vm.networkInterfaces { // network device - nd, err := buildNetworkDevice(finder, network.label, "vmxnet3") + var networkDeviceType string + if vm.template == "" { + networkDeviceType = "e1000" + } else { + networkDeviceType = "vmxnet3" + } + nd, err := buildNetworkDevice(finder, network.label, networkDeviceType) if err != nil { return err } networkDevices = append(networkDevices, nd) - var ipSetting types.CustomizationIPSettings - if network.ipv4Address == "" { - ipSetting.Ip = &types.CustomizationDhcpIpGenerator{} - } else { - if network.ipv4PrefixLength == 0 { - return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") + if vm.template != "" { + var ipSetting types.CustomizationIPSettings + if network.ipv4Address == "" { + ipSetting.Ip = &types.CustomizationDhcpIpGenerator{} + } else { + if network.ipv4PrefixLength == 0 { + return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") + } + m := net.CIDRMask(network.ipv4PrefixLength, 32) + sm := net.IPv4(m[0], m[1], m[2], m[3]) + subnetMask := sm.String() + log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway) + log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address) + log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength) + log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask) + ipSetting.Gateway = []string{ + network.ipv4Gateway, + } + ipSetting.Ip = &types.CustomizationFixedIp{ + IpAddress: network.ipv4Address, + } + ipSetting.SubnetMask = subnetMask } - m := net.CIDRMask(network.ipv4PrefixLength, 32) - sm := net.IPv4(m[0], m[1], m[2], m[3]) - subnetMask := sm.String() - log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway) - log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address) - log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength) - log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask) - ipSetting.Gateway = []string{ - network.ipv4Gateway, - } - ipSetting.Ip = &types.CustomizationFixedIp{ - IpAddress: network.ipv4Address, - } - ipSetting.SubnetMask = subnetMask - } - ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{} - if network.ipv6Address == "" { - ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ - &types.CustomizationDhcpIpV6Generator{}, - } - } else { - log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway) - log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address) - log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength) + ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{} + if network.ipv6Address == "" { + ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ + &types.CustomizationDhcpIpV6Generator{}, + } + } else { + log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway) + log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address) + log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength) - ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ - &types.CustomizationFixedIpV6{ - IpAddress: network.ipv6Address, - SubnetMask: int32(network.ipv6PrefixLength), - }, + ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ + &types.CustomizationFixedIpV6{ + IpAddress: network.ipv6Address, + SubnetMask: int32(network.ipv6PrefixLength), + }, + } + ipv6Spec.Gateway = []string{network.ipv6Gateway} } - ipv6Spec.Gateway = []string{network.ipv6Gateway} - } - ipSetting.IpV6Spec = ipv6Spec + ipSetting.IpV6Spec = ipv6Spec - // network config - config := types.CustomizationAdapterMapping{ - Adapter: ipSetting, + // network config + config := types.CustomizationAdapterMapping{ + Adapter: ipSetting, + } + networkConfigs = append(networkConfigs, config) } - networkConfigs = append(networkConfigs, config) } - log.Printf("[DEBUG] network configs: %v", networkConfigs[0].Adapter) + log.Printf("[DEBUG] network devices: %v", networkDevices) + log.Printf("[DEBUG] network configs: %v", networkConfigs) - // make config spec - configSpec := types.VirtualMachineConfigSpec{ - NumCPUs: vm.vcpu, - NumCoresPerSocket: 1, - MemoryMB: vm.memoryMb, - MemoryAllocation: &types.ResourceAllocationInfo{ - Reservation: vm.memoryAllocation.reservation, - }, - } - - log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) - - log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations) - - // make ExtraConfig - if len(vm.customConfigurations) > 0 { - var ov []types.BaseOptionValue - for k, v := range vm.customConfigurations { - key := k - value := v - o := types.OptionValue{ - Key: key, - Value: &value, - } - ov = append(ov, &o) + var task *object.Task + if vm.template == "" { + var mds mo.Datastore + if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil { + return err } - configSpec.ExtraConfig = ov - log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) - } - - var template_mo mo.VirtualMachine - err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) - - var identity_options types.BaseCustomizationIdentitySettings - if strings.HasPrefix(template_mo.Config.GuestId, "win") { - var timeZone int - if vm.timeZone == "Etc/UTC" { - vm.timeZone = "085" - } - timeZone, err := strconv.Atoi(vm.timeZone) + log.Printf("[DEBUG] datastore: %#v", mds.Name) + scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi") if err != nil { - return fmt.Errorf("Error converting TimeZone: %s", err) + log.Printf("[ERROR] %s", err) } - guiUnattended := types.CustomizationGuiUnattended{ - AutoLogon: false, - AutoLogonCount: 1, - TimeZone: int32(timeZone), + configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{ + Operation: types.VirtualDeviceConfigSpecOperationAdd, + Device: scsi, + }) + + configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} + + task, err = folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) + if err != nil { + log.Printf("[ERROR] %s", err) } - customIdentification := types.CustomizationIdentification{} - - userData := types.CustomizationUserData{ - ComputerName: &types.CustomizationFixedName{ - Name: strings.Split(vm.name, ".")[0], - }, - ProductId: vm.windowsOptionalConfig.productKey, - FullName: "terraform", - OrgName: "terraform", + err = task.Wait(context.TODO()) + if err != nil { + log.Printf("[ERROR] %s", err) } - if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" { - customIdentification.DomainAdminPassword = &types.CustomizationPassword{ - PlainText: true, - Value: vm.windowsOptionalConfig.domainUserPassword, - } - customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser - customIdentification.JoinDomain = vm.windowsOptionalConfig.domain - } - - if vm.windowsOptionalConfig.adminPassword != "" { - guiUnattended.Password = &types.CustomizationPassword{ - PlainText: true, - Value: vm.windowsOptionalConfig.adminPassword, - } - } - - identity_options = &types.CustomizationSysprep{ - GuiUnattended: guiUnattended, - Identification: customIdentification, - UserData: userData, - } } else { - identity_options = &types.CustomizationLinuxPrep{ - HostName: &types.CustomizationFixedName{ - Name: strings.Split(vm.name, ".")[0], - }, - Domain: vm.domain, - TimeZone: vm.timeZone, - HwClockUTC: types.NewBool(true), - } - } - // create CustomizationSpec - customSpec := types.CustomizationSpec{ - Identity: identity_options, - GlobalIPSettings: types.CustomizationGlobalIPSettings{ - DnsSuffixList: vm.dnsSuffixes, - DnsServerList: vm.dnsServers, - }, - NicSettingMap: networkConfigs, - } - log.Printf("[DEBUG] custom spec: %v", customSpec) - - // make vm clone spec - cloneSpec := types.VirtualMachineCloneSpec{ - Location: relocateSpec, - Template: false, - Config: &configSpec, - PowerOn: false, - } - if vm.linkedClone { + relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType) if err != nil { - return fmt.Errorf("Error reading base VM properties: %s", err) + return err } - if template_mo.Snapshot == nil { - return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots") - } - cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot - } - log.Printf("[DEBUG] clone spec: %v", cloneSpec) - task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec) - if err != nil { - return err + log.Printf("[DEBUG] relocate spec: %v", relocateSpec) + + // make vm clone spec + cloneSpec := types.VirtualMachineCloneSpec{ + Location: relocateSpec, + Template: false, + Config: &configSpec, + PowerOn: false, + } + if vm.linkedClone { + if template_mo.Snapshot == nil { + return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots") + } + cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot + } + log.Printf("[DEBUG] clone spec: %v", cloneSpec) + + task, err = template.Clone(context.TODO(), folder, vm.name, cloneSpec) + if err != nil { + return err + } } - _, err = task.WaitForResult(context.TODO(), nil) + err = task.Wait(context.TODO()) if err != nil { - return err + log.Printf("[ERROR] %s", err) } newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) @@ -1667,9 +1457,92 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { return err } - if vm.skipCustomization { + firstDisk := 0 + if vm.template != "" { + firstDisk++ + } + for i := firstDisk; i < len(vm.hardDisks); i++ { + log.Printf("[DEBUG] disk index: %v", i) + err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, vm.hardDisks[i].vmdkPath) + if err != nil { + return err + } + } + + if vm.skipCustomization || vm.template == "" { log.Printf("[DEBUG] VM customization skipped") } else { + var identity_options types.BaseCustomizationIdentitySettings + if strings.HasPrefix(template_mo.Config.GuestId, "win") { + var timeZone int + if vm.timeZone == "Etc/UTC" { + vm.timeZone = "085" + } + timeZone, err := strconv.Atoi(vm.timeZone) + if err != nil { + return fmt.Errorf("Error converting TimeZone: %s", err) + } + + guiUnattended := types.CustomizationGuiUnattended{ + AutoLogon: false, + AutoLogonCount: 1, + TimeZone: int32(timeZone), + } + + customIdentification := types.CustomizationIdentification{} + + userData := types.CustomizationUserData{ + ComputerName: &types.CustomizationFixedName{ + Name: strings.Split(vm.name, ".")[0], + }, + ProductId: vm.windowsOptionalConfig.productKey, + FullName: "terraform", + OrgName: "terraform", + } + + if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" { + customIdentification.DomainAdminPassword = &types.CustomizationPassword{ + PlainText: true, + Value: vm.windowsOptionalConfig.domainUserPassword, + } + customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser + customIdentification.JoinDomain = vm.windowsOptionalConfig.domain + } + + if vm.windowsOptionalConfig.adminPassword != "" { + guiUnattended.Password = &types.CustomizationPassword{ + PlainText: true, + Value: vm.windowsOptionalConfig.adminPassword, + } + } + + identity_options = &types.CustomizationSysprep{ + GuiUnattended: guiUnattended, + Identification: customIdentification, + UserData: userData, + } + } else { + identity_options = &types.CustomizationLinuxPrep{ + HostName: &types.CustomizationFixedName{ + Name: strings.Split(vm.name, ".")[0], + }, + Domain: vm.domain, + TimeZone: vm.timeZone, + HwClockUTC: types.NewBool(true), + } + } + + // create CustomizationSpec + customSpec := types.CustomizationSpec{ + Identity: identity_options, + GlobalIPSettings: types.CustomizationGlobalIPSettings{ + DnsSuffixList: vm.dnsSuffixes, + DnsServerList: vm.dnsServers, + }, + NicSettingMap: networkConfigs, + } + log.Printf("[DEBUG] custom spec: %v", customSpec) + log.Printf("[DEBUG] VM customization starting") taskb, err := newVM.Customize(context.TODO(), customSpec) if err != nil { @@ -1682,16 +1555,8 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { log.Printf("[DEBUG] VM customization finished") } - for i := 1; i < len(vm.hardDisks); i++ { - err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, vm.hardDisks[i].vmdkPath) - if err != nil { - return err - } + if vm.bootableVmdk || vm.template != "" { + newVM.PowerOn(context.TODO()) } - - log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) - - newVM.PowerOn(context.TODO()) - return nil }