diff --git a/README.md b/README.md index 1762973c..89e82c44 100644 --- a/README.md +++ b/README.md @@ -426,7 +426,9 @@ This resource doesn't expose any additional attributes. * `vm_id` - (Optional) The ID ###### Attributes -This resource doesn't expose any additional attributes. +* `ipv4_addresses` - The IPv4 addresses per network interface published by the QEMU agent (empty list when `agent.enabled` is `false`) +* `ipv6_addresses` - The IPv6 addresses per network interface published by the QEMU agent (empty list when `agent.enabled` is `false`) +* `network_interface_names` - The network interface names published by the QEMU agent (empty list when `agent.enabled` is `false`) ## Developing the Provider If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.13+ is *required*). You'll also need to correctly setup a [GOPATH](http://golang.org/doc/code.html#GOPATH), as well as adding `$GOPATH/bin` to your `$PATH`. diff --git a/example/resource_virtual_environment_file.tf b/example/resource_virtual_environment_file.tf index d7a9be56..9cd0333c 100644 --- a/example/resource_virtual_environment_file.tf +++ b/example/resource_virtual_environment_file.tf @@ -8,7 +8,7 @@ resource "proxmox_virtual_environment_file" "ubuntu_cloud_image" { } } -resource "proxmox_virtual_environment_file" "cloud_init_config" { +resource "proxmox_virtual_environment_file" "cloud_config" { content_type = "snippets" datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}" node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}" @@ -19,7 +19,7 @@ resource "proxmox_virtual_environment_file" "cloud_init_config" { chpasswd: list: | ubuntu:example - expire: False + expire: false hostname: terraform-provider-proxmox-example packages: - qemu-guest-agent @@ -33,7 +33,7 @@ users: sudo: ALL=(ALL) NOPASSWD:ALL EOF - file_name = "terraform-provider-proxmox-example-cloud-init.yaml" + file_name = "terraform-provider-proxmox-example-cloud-config.yaml" } } diff --git a/example/resource_virtual_environment_vm.tf b/example/resource_virtual_environment_vm.tf index 46c135cf..aeea83e4 100644 --- a/example/resource_virtual_environment_vm.tf +++ b/example/resource_virtual_environment_vm.tf @@ -16,11 +16,11 @@ resource "proxmox_virtual_environment_vm" "example" { user_account { keys = ["${trimspace(tls_private_key.example.public_key_openssh)}"] - password = "proxmoxtf" + password = "example" username = "ubuntu" } - user_data_file_id = "${proxmox_virtual_environment_file.cloud_init_config.id}" + user_data_file_id = "${proxmox_virtual_environment_file.cloud_config.id}" } disk { @@ -34,6 +34,20 @@ resource "proxmox_virtual_environment_vm" "example" { os_type = "l26" pool_id = "${proxmox_virtual_environment_pool.example.id}" vm_id = 2038 + + connection { + type = "ssh" + agent = false + host = "${element(element(self.ipv4_addresses, index(self.network_interface_names, "eth0")), 0)}" + private_key = "${tls_private_key.example.private_key_pem}" + user = "ubuntu" + } + + provisioner "remote-exec" { + inline = [ + "echo Welcome to $(hostname)!", + ] + } } resource "local_file" "example_ssh_private_key" { @@ -54,3 +68,15 @@ resource "tls_private_key" "example" { output "resource_proxmox_virtual_environment_vm_example_id" { value = "${proxmox_virtual_environment_vm.example.id}" } + +output "resource_proxmox_virtual_environment_vm_example_ipv4_addresses" { + value = "${proxmox_virtual_environment_vm.example.ipv4_addresses}" +} + +output "resource_proxmox_virtual_environment_vm_example_ipv6_addresses" { + value = "${proxmox_virtual_environment_vm.example.ipv6_addresses}" +} + +output "resource_proxmox_virtual_environment_vm_example_network_interface_names" { + value = "${proxmox_virtual_environment_vm.example.network_interface_names}" +} diff --git a/proxmox/virtual_environment_vm.go b/proxmox/virtual_environment_vm.go index 61ada9ee..73af65ef 100644 --- a/proxmox/virtual_environment_vm.go +++ b/proxmox/virtual_environment_vm.go @@ -66,6 +66,22 @@ VMID: return nil, errors.New("Unable to retrieve the next available VM identifier") } +// GetVMNetworkInterfacesFromAgent retrieves the network interfaces reported by the QEMU agent. +func (c *VirtualEnvironmentClient) GetVMNetworkInterfacesFromAgent(nodeName string, vmID int) (*VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData, error) { + resBody := &VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseBody{} + err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/qemu/%d/agent/network-get-interfaces", url.PathEscape(nodeName), vmID), nil, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + return resBody.Data, nil +} + // GetVMStatus retrieves the status for a virtual machine. func (c *VirtualEnvironmentClient) GetVMStatus(nodeName string, vmID int) (*VirtualEnvironmentVMGetStatusResponseData, error) { resBody := &VirtualEnvironmentVMGetStatusResponseBody{} @@ -112,6 +128,32 @@ func (c *VirtualEnvironmentClient) UpdateVMAsync(nodeName string, vmID int, d *V return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID), d, nil) } +// WaitForNetworkInterfacesFromAgent waits for a virtual machine's QEMU agent to publish the network interfaces. +func (c *VirtualEnvironmentClient) WaitForNetworkInterfacesFromAgent(nodeName string, vmID int, timeout int, delay int) (*VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData, error) { + timeDelay := int64(delay) + timeMax := float64(timeout) + timeStart := time.Now() + timeElapsed := timeStart.Sub(timeStart) + + for timeElapsed.Seconds() < timeMax { + if int64(timeElapsed.Seconds())%timeDelay == 0 { + data, err := c.GetVMNetworkInterfacesFromAgent(nodeName, vmID) + + if err == nil && data != nil { + return data, err + } + + time.Sleep(1 * time.Second) + } + + time.Sleep(200 * time.Millisecond) + + timeElapsed = time.Now().Sub(timeStart) + } + + return nil, fmt.Errorf("Timeout while waiting for the QEMU agent on VM \"%d\" to publish the network interfaces", vmID) +} + // WaitForState waits for a virtual machine to reach a specific state. func (c *VirtualEnvironmentClient) WaitForState(nodeName string, vmID int, state string, timeout int, delay int) error { state = strings.ToLower(state) @@ -141,5 +183,5 @@ func (c *VirtualEnvironmentClient) WaitForState(nodeName string, vmID int, state timeElapsed = time.Now().Sub(timeStart) } - return fmt.Errorf("Failed to wait for VM \"%d\" to enter the state \"%s\"", vmID, state) + return fmt.Errorf("Timeout while waiting for VM \"%d\" to enter the state \"%s\"", vmID, state) } diff --git a/proxmox/virtual_environment_vm_types.go b/proxmox/virtual_environment_vm_types.go index 9507d20a..977a7b44 100644 --- a/proxmox/virtual_environment_vm_types.go +++ b/proxmox/virtual_environment_vm_types.go @@ -258,6 +258,43 @@ type VirtualEnvironmentVMCreateRequestBody struct { WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty" url:"watchdog,omitempty"` } +// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseBody contains the body from a QEMU get network interfaces response. +type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseBody struct { + Data *VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData `json:"data,omitempty"` +} + +// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData contains the data from a QEMU get network interfaces response. +type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData struct { + Result *[]VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResult `json:"result,omitempty"` +} + +// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResult contains the result from a QEMU get network interfaces response. +type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResult struct { + MACAddress string `json:"hardware-address"` + Name string `json:"name"` + Statistics VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics `json:"statistics"` + IPAddresses *[]VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultIPAddress `json:"ip-addresses,omitempty"` +} + +// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultIPAddress contains the IP address from a QEMU get network interfaces response. +type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultIPAddress struct { + Address string `json:"ip-address"` + Prefix int `json:"prefix"` + Type string `json:"ip-address-type"` +} + +// VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics contains the statistics from a QEMU get network interfaces response. +type VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseResultStatistics struct { + RXBytes int `json:"rx-bytes"` + RXDropped int `json:"rx-dropped"` + RXErrors int `json:"rx-errs"` + RXPackets int `json:"rx-packets"` + TXBytes int `json:"tx-bytes"` + TXDropped int `json:"tx-dropped"` + TXErrors int `json:"tx-errs"` + TXPackets int `json:"tx-packets"` +} + // VirtualEnvironmentVMGetResponseBody contains the body from an virtual machine get response. type VirtualEnvironmentVMGetResponseBody struct { Data *VirtualEnvironmentVMGetResponseData `json:"data,omitempty"` diff --git a/proxmoxtf/resource_virtual_environment_vm.go b/proxmoxtf/resource_virtual_environment_vm.go index 1cd2081d..ecedf3f1 100644 --- a/proxmoxtf/resource_virtual_environment_vm.go +++ b/proxmoxtf/resource_virtual_environment_vm.go @@ -89,6 +89,8 @@ const ( mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable = "read_burstable" mkResourceVirtualEnvironmentVMDiskSpeedWrite = "write" mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = "write_burstable" + mkResourceVirtualEnvironmentVMIPv4Addresses = "ipv4_addresses" + mkResourceVirtualEnvironmentVMIPv6Addresses = "ipv6_addresses" mkResourceVirtualEnvironmentVMKeyboardLayout = "keyboard_layout" mkResourceVirtualEnvironmentVMMemory = "memory" mkResourceVirtualEnvironmentVMMemoryDedicated = "dedicated" @@ -101,6 +103,7 @@ const ( mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress = "mac_address" mkResourceVirtualEnvironmentVMNetworkDeviceModel = "model" mkResourceVirtualEnvironmentVMNetworkDeviceVLANIDs = "vlan_ids" + mkResourceVirtualEnvironmentVMNetworkInterfaceNames = "network_interface_names" mkResourceVirtualEnvironmentVMNodeName = "node_name" mkResourceVirtualEnvironmentVMOSType = "os_type" mkResourceVirtualEnvironmentVMPoolID = "pool_id" @@ -193,7 +196,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The cloud-init configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -202,7 +205,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The DNS configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -228,7 +231,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The IP configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -237,7 +240,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The IPv4 configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -263,7 +266,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The IPv6 configuration", Optional: true, DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -294,7 +297,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The user account configuration", Required: true, DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -487,6 +490,24 @@ func resourceVirtualEnvironmentVM() *schema.Resource { MaxItems: 14, MinItems: 0, }, + mkResourceVirtualEnvironmentVMIPv4Addresses: { + Type: schema.TypeList, + Computed: true, + Description: "The IPv4 addresses published by the QEMU agent", + Elem: &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + mkResourceVirtualEnvironmentVMIPv6Addresses: { + Type: schema.TypeList, + Computed: true, + Description: "The IPv6 addresses published by the QEMU agent", + Elem: &schema.Schema{ + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, mkResourceVirtualEnvironmentVMKeyboardLayout: { Type: schema.TypeString, Optional: true, @@ -591,7 +612,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Optional: true, Description: "The VLAN identifiers", DefaultFunc: func() (interface{}, error) { - return make([]interface{}, 0), nil + return []interface{}{}, nil }, Elem: &schema.Schema{Type: schema.TypeInt}, }, @@ -600,6 +621,12 @@ func resourceVirtualEnvironmentVM() *schema.Resource { MaxItems: 8, MinItems: 0, }, + mkResourceVirtualEnvironmentVMNetworkInterfaceNames: { + Type: schema.TypeList, + Computed: true, + Description: "The network interface names published by the QEMU agent", + Elem: &schema.Schema{Type: schema.TypeString}, + }, mkResourceVirtualEnvironmentVMNodeName: &schema.Schema{ Type: schema.TypeString, Description: "The node name", @@ -649,37 +676,24 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e return err } - resourceSchema := resourceVirtualEnvironmentVM().Schema - agent := d.Get(mkResourceVirtualEnvironmentVMAgent).([]interface{}) + resource := resourceVirtualEnvironmentVM() - if len(agent) == 0 { - agentDefault, err := resourceSchema[mkResourceVirtualEnvironmentVMAgent].DefaultValue() + agentBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMAgent}, 0, true) - if err != nil { - return err - } - - agent = agentDefault.([]interface{}) + if err != nil { + return err } - agentBlock := agent[0].(map[string]interface{}) agentEnabled := proxmox.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentEnabled].(bool)) agentTrim := proxmox.CustomBool(agentBlock[mkResourceVirtualEnvironmentVMAgentTrim].(bool)) agentType := agentBlock[mkResourceVirtualEnvironmentVMAgentType].(string) - cdrom := d.Get(mkResourceVirtualEnvironmentVMCDROM).([]interface{}) + cdromBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMCDROM}, 0, true) - if len(cdrom) == 0 { - cdromDefault, err := resourceSchema[mkResourceVirtualEnvironmentVMCDROM].DefaultValue() - - if err != nil { - return err - } - - cdrom = cdromDefault.([]interface{}) + if err != nil { + return err } - cdromBlock := cdrom[0].(map[string]interface{}) cdromEnabled := cdromBlock[mkResourceVirtualEnvironmentVMCDROMEnabled].(bool) cdromFileID := cdromBlock[mkResourceVirtualEnvironmentVMCDROMFileID].(string) @@ -790,19 +804,12 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e } } - cpu := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{}) + cpuBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMCPU}, 0, true) - if len(cpu) == 0 { - cpuDefault, err := resourceSchema[mkResourceVirtualEnvironmentVMCPU].DefaultValue() - - if err != nil { - return err - } - - cpu = cpuDefault.([]interface{}) + if err != nil { + return err } - cpuBlock := cpu[0].(map[string]interface{}) cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int) cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int) cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int) @@ -811,52 +818,20 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e disk := d.Get(mkResourceVirtualEnvironmentVMDisk).([]interface{}) scsiDevices := make(proxmox.CustomStorageDevices, len(disk)) - diskSchemaElem := resourceSchema[mkResourceVirtualEnvironmentVMDisk].Elem - diskSchemaResource := diskSchemaElem.(*schema.Resource) - diskSpeedResource := diskSchemaResource.Schema[mkResourceVirtualEnvironmentVMDiskSpeed] - - for i, d := range disk { - block := d.(map[string]interface{}) - - datastoreID, _ := block[mkResourceVirtualEnvironmentVMDiskDatastoreID].(string) - fileID, _ := block[mkResourceVirtualEnvironmentVMDiskFileID].(string) - size, _ := block[mkResourceVirtualEnvironmentVMDiskSize].(int) - speed := block[mkResourceVirtualEnvironmentVMDiskSpeed].([]interface{}) - - if len(speed) == 0 { - diskSpeedDefault, err := diskSpeedResource.DefaultValue() - - if err != nil { - return err - } - - speed = diskSpeedDefault.([]interface{}) - } - - speedBlock := speed[0].(map[string]interface{}) - speedLimitRead := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedRead].(int) - speedLimitReadBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable].(int) - speedLimitWrite := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWrite].(int) - speedLimitWriteBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable].(int) - + for i, diskEntry := range disk { diskDevice := proxmox.CustomStorageDevice{ Enabled: true, } - if speedLimitRead > 0 { - diskDevice.MaxReadSpeedMbps = &speedLimitRead - } + block := diskEntry.(map[string]interface{}) + datastoreID, _ := block[mkResourceVirtualEnvironmentVMDiskDatastoreID].(string) + fileID, _ := block[mkResourceVirtualEnvironmentVMDiskFileID].(string) + size, _ := block[mkResourceVirtualEnvironmentVMDiskSize].(int) - if speedLimitReadBurstable > 0 { - diskDevice.BurstableReadSpeedMbps = &speedLimitReadBurstable - } + speedBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMDiskSpeed}, 0, false) - if speedLimitWrite > 0 { - diskDevice.MaxWriteSpeedMbps = &speedLimitWrite - } - - if speedLimitWriteBurstable > 0 { - diskDevice.BurstableWriteSpeedMbps = &speedLimitWriteBurstable + if err != nil { + return err } if fileID != "" { @@ -865,23 +840,39 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e diskDevice.FileVolume = fmt.Sprintf("%s:%d", datastoreID, size) } + if len(speedBlock) > 0 { + speedLimitRead := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedRead].(int) + speedLimitReadBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable].(int) + speedLimitWrite := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWrite].(int) + speedLimitWriteBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable].(int) + + if speedLimitRead > 0 { + diskDevice.MaxReadSpeedMbps = &speedLimitRead + } + + if speedLimitReadBurstable > 0 { + diskDevice.BurstableReadSpeedMbps = &speedLimitReadBurstable + } + + if speedLimitWrite > 0 { + diskDevice.MaxWriteSpeedMbps = &speedLimitWrite + } + + if speedLimitWriteBurstable > 0 { + diskDevice.BurstableWriteSpeedMbps = &speedLimitWriteBurstable + } + } + scsiDevices[i] = diskDevice } keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string) - memory := d.Get(mkResourceVirtualEnvironmentVMMemory).([]interface{}) + memoryBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMMemory}, 0, true) - if len(memory) == 0 { - memoryDefault, err := resourceSchema[mkResourceVirtualEnvironmentVMMemory].DefaultValue() - - if err != nil { - return err - } - - memory = memoryDefault.([]interface{}) + if err != nil { + return err } - memoryBlock := memory[0].(map[string]interface{}) memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentVMMemoryDedicated].(int) memoryFloating := memoryBlock[mkResourceVirtualEnvironmentVMMemoryFloating].(int) memoryShared := memoryBlock[mkResourceVirtualEnvironmentVMMemoryShared].(int) @@ -891,8 +882,8 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{}) networkDeviceObjects := make(proxmox.CustomNetworkDevices, len(networkDevice)) - for i, d := range networkDevice { - block := d.(map[string]interface{}) + for i, networkDeviceEntry := range networkDevice { + block := networkDeviceEntry.(map[string]interface{}) bridge, _ := block[mkResourceVirtualEnvironmentVMNetworkDeviceBridge].(string) enabled, _ := block[mkResourceVirtualEnvironmentVMNetworkDeviceEnabled].(bool) @@ -1540,6 +1531,43 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err d.Set(mkResourceVirtualEnvironmentVMStarted, status.Status == "running") + // Populate the attributes that rely on the QEMU agent. + ipv4Addresses := []interface{}{} + ipv6Addresses := []interface{}{} + networkInterfaceNames := []interface{}{} + + if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled { + networkInterfaces, err := veClient.WaitForNetworkInterfacesFromAgent(nodeName, vmID, 600, 5) + + if err == nil && networkInterfaces.Result != nil { + ipv4Addresses = make([]interface{}, len(*networkInterfaces.Result)) + ipv6Addresses = make([]interface{}, len(*networkInterfaces.Result)) + networkInterfaceNames = make([]interface{}, len(*networkInterfaces.Result)) + + for ri, rv := range *networkInterfaces.Result { + rvIPv4Addresses := []interface{}{} + rvIPv6Addresses := []interface{}{} + + for _, ip := range *rv.IPAddresses { + switch ip.Type { + case "ipv4": + rvIPv4Addresses = append(rvIPv4Addresses, ip.Address) + case "ipv6": + rvIPv6Addresses = append(rvIPv6Addresses, ip.Address) + } + } + + ipv4Addresses[ri] = rvIPv4Addresses + ipv6Addresses[ri] = rvIPv6Addresses + networkInterfaceNames[ri] = rv.Name + } + } + } + + d.Set(mkResourceVirtualEnvironmentVMIPv4Addresses, ipv4Addresses) + d.Set(mkResourceVirtualEnvironmentVMIPv6Addresses, ipv6Addresses) + d.Set(mkResourceVirtualEnvironmentVMNetworkInterfaceNames, networkInterfaceNames) + return nil } diff --git a/proxmoxtf/resource_virtual_environment_vm_test.go b/proxmoxtf/resource_virtual_environment_vm_test.go index 655a62b5..85a9e068 100644 --- a/proxmoxtf/resource_virtual_environment_vm_test.go +++ b/proxmoxtf/resource_virtual_environment_vm_test.go @@ -43,16 +43,25 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMVMID, }) + testComputedAttributes(t, s, []string{ + mkResourceVirtualEnvironmentVMIPv4Addresses, + mkResourceVirtualEnvironmentVMIPv6Addresses, + mkResourceVirtualEnvironmentVMNetworkInterfaceNames, + }) + testSchemaValueTypes(t, s, []string{ mkResourceVirtualEnvironmentVMCDROM, mkResourceVirtualEnvironmentVMCloudInit, mkResourceVirtualEnvironmentVMCPU, mkResourceVirtualEnvironmentVMDescription, mkResourceVirtualEnvironmentVMDisk, + mkResourceVirtualEnvironmentVMIPv4Addresses, + mkResourceVirtualEnvironmentVMIPv6Addresses, mkResourceVirtualEnvironmentVMKeyboardLayout, mkResourceVirtualEnvironmentVMMemory, mkResourceVirtualEnvironmentVMName, mkResourceVirtualEnvironmentVMNetworkDevice, + mkResourceVirtualEnvironmentVMNetworkInterfaceNames, mkResourceVirtualEnvironmentVMOSType, mkResourceVirtualEnvironmentVMPoolID, mkResourceVirtualEnvironmentVMStarted, @@ -63,10 +72,13 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { schema.TypeList, schema.TypeString, schema.TypeList, + schema.TypeList, + schema.TypeList, schema.TypeString, schema.TypeList, schema.TypeString, schema.TypeList, + schema.TypeList, schema.TypeString, schema.TypeString, schema.TypeBool, diff --git a/proxmoxtf/utils.go b/proxmoxtf/utils.go index c48a70aa..d02db38d 100644 --- a/proxmoxtf/utils.go +++ b/proxmoxtf/utils.go @@ -131,6 +131,50 @@ func getQEMUAgentTypeValidator() schema.SchemaValidateFunc { return validation.StringInSlice([]string{"isa", "virtio"}, false) } +func getSchemaBlock(r *schema.Resource, d *schema.ResourceData, m interface{}, k []string, i int, allowDefault bool) (map[string]interface{}, error) { + var resourceBlock map[string]interface{} + var resourceData interface{} + var resourceSchema *schema.Schema + + for ki, kv := range k { + if ki == 0 { + resourceData = d.Get(kv) + resourceSchema = r.Schema[kv] + } else { + mapValues := resourceData.([]interface{}) + + if len(mapValues) <= i { + return resourceBlock, fmt.Errorf("Index out of bounds %d", i) + } + + mapValue := mapValues[i].(map[string]interface{}) + + resourceData = mapValue[kv] + resourceSchema = resourceSchema.Elem.(*schema.Resource).Schema[kv] + } + } + + list := resourceData.([]interface{}) + + if len(list) == 0 { + if allowDefault { + listDefault, err := resourceSchema.DefaultValue() + + if err != nil { + return nil, err + } + + list = listDefault.([]interface{}) + } + } + + if len(list) > i { + resourceBlock = list[i].(map[string]interface{}) + } + + return resourceBlock, nil +} + func getVLANIDsValidator() schema.SchemaValidateFunc { return func(i interface{}, k string) (ws []string, es []error) { min := 1