0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

chore(vm): refactoring: extract network device code from vm.go (#1127)

chore(vm): refactoring: extract network code

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-03-15 21:06:30 -04:00 committed by GitHub
parent 279b41a0e4
commit 29b5438faf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 837 additions and 742 deletions

View File

@ -28,7 +28,7 @@ func TestDatastoresInstantiation(t *testing.T) {
func TestDatastoresSchema(t *testing.T) { func TestDatastoresSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Datastores() s := Datastores().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentDatastoresNodeName, mkDataSourceVirtualEnvironmentDatastoresNodeName,

View File

@ -28,7 +28,7 @@ func TestDNSInstantiation(t *testing.T) {
func TestDNSSchema(t *testing.T) { func TestDNSSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := DNS() s := DNS().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentDNSNodeName, mkDataSourceVirtualEnvironmentDNSNodeName,

View File

@ -25,18 +25,18 @@ func TestAliasSchemaInstantiation(t *testing.T) {
func TestAliasSchema(t *testing.T) { func TestAliasSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: AliasSchema()} s := AliasSchema()
test.AssertRequiredArguments(t, &r, []string{ test.AssertRequiredArguments(t, s, []string{
mkAliasName, mkAliasName,
}) })
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkAliasCIDR, mkAliasCIDR,
mkAliasComment, mkAliasComment,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkAliasName: schema.TypeString, mkAliasName: schema.TypeString,
mkAliasCIDR: schema.TypeString, mkAliasCIDR: schema.TypeString,
mkAliasComment: schema.TypeString, mkAliasComment: schema.TypeString,

View File

@ -25,13 +25,13 @@ func TestAliasesSchemaInstantiation(t *testing.T) {
func TestAliasesSchema(t *testing.T) { func TestAliasesSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: AliasesSchema()} s := AliasesSchema()
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkAliasesAliasNames, mkAliasesAliasNames,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkAliasesAliasNames: schema.TypeList, mkAliasesAliasNames: schema.TypeList,
}) })
} }

View File

@ -25,24 +25,24 @@ func TestIPSetSchemaInstantiation(t *testing.T) {
func TestIPSetSchema(t *testing.T) { func TestIPSetSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: IPSetSchema()} s := IPSetSchema()
test.AssertRequiredArguments(t, &r, []string{ test.AssertRequiredArguments(t, s, []string{
mkIPSetName, mkIPSetName,
}) })
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkIPSetCIDR, mkIPSetCIDR,
mkIPSetCIDRComment, mkIPSetCIDRComment,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkIPSetName: schema.TypeString, mkIPSetName: schema.TypeString,
mkIPSetCIDR: schema.TypeList, mkIPSetCIDR: schema.TypeList,
mkIPSetCIDRComment: schema.TypeString, mkIPSetCIDRComment: schema.TypeString,
}) })
cird := test.AssertNestedSchemaExistence(t, &r, mkIPSetCIDR) cird := test.AssertNestedSchemaExistence(t, s, mkIPSetCIDR)
test.AssertComputedAttributes(t, cird, []string{ test.AssertComputedAttributes(t, cird, []string{
mkIPSetCIDRName, mkIPSetCIDRName,

View File

@ -25,13 +25,13 @@ func TestIPSetsSchemaInstantiation(t *testing.T) {
func TestIPSetsSchema(t *testing.T) { func TestIPSetsSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: IPSetsSchema()} s := IPSetsSchema()
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkIPSetsIPSetNames, mkIPSetsIPSetNames,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkIPSetsIPSetNames: schema.TypeList, mkIPSetsIPSetNames: schema.TypeList,
}) })
} }

View File

@ -25,14 +25,14 @@ func TestRuleSchemaInstantiation(t *testing.T) {
func TestRuleSchema(t *testing.T) { func TestRuleSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: RuleSchema()} s := RuleSchema()
test.AssertRequiredArguments(t, &r, []string{ test.AssertRequiredArguments(t, s, []string{
mkRuleAction, mkRuleAction,
mkRuleType, mkRuleType,
}) })
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkRuleComment, mkRuleComment,
mkRuleDest, mkRuleDest,
mkRuleDPort, mkRuleDPort,
@ -45,7 +45,7 @@ func TestRuleSchema(t *testing.T) {
mkRuleSPort, mkRuleSPort,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkRulePos: schema.TypeInt, mkRulePos: schema.TypeInt,
mkRuleAction: schema.TypeString, mkRuleAction: schema.TypeString,
mkRuleType: schema.TypeString, mkRuleType: schema.TypeString,

View File

@ -25,18 +25,18 @@ func TestSecurityGroupSchemaInstantiation(t *testing.T) {
func TestSecurityGroupSchema(t *testing.T) { func TestSecurityGroupSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: SecurityGroupSchema()} s := SecurityGroupSchema()
test.AssertRequiredArguments(t, &r, []string{ test.AssertRequiredArguments(t, s, []string{
mkSecurityGroupName, mkSecurityGroupName,
}) })
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkSecurityGroupComment, mkSecurityGroupComment,
mkRules, mkRules,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkSecurityGroupName: schema.TypeString, mkSecurityGroupName: schema.TypeString,
mkSecurityGroupComment: schema.TypeString, mkSecurityGroupComment: schema.TypeString,
mkRules: schema.TypeList, mkRules: schema.TypeList,

View File

@ -25,13 +25,13 @@ func TestSecurityGroupsSchemaInstantiation(t *testing.T) {
func TestSecurityGroupsSchema(t *testing.T) { func TestSecurityGroupsSchema(t *testing.T) {
t.Parallel() t.Parallel()
r := schema.Resource{Schema: SecurityGroupsSchema()} s := SecurityGroupsSchema()
test.AssertComputedAttributes(t, &r, []string{ test.AssertComputedAttributes(t, s, []string{
mkSecurityGroupsSecurityGroupNames, mkSecurityGroupsSecurityGroupNames,
}) })
test.AssertValueTypes(t, &r, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkSecurityGroupsSecurityGroupNames: schema.TypeList, mkSecurityGroupsSecurityGroupNames: schema.TypeList,
}) })
} }

View File

@ -28,7 +28,7 @@ func TestGroupInstantiation(t *testing.T) {
func TestGroupSchema(t *testing.T) { func TestGroupSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Group() s := Group().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentGroupID, mkDataSourceVirtualEnvironmentGroupID,

View File

@ -28,7 +28,7 @@ func TestGroupsInstantiation(t *testing.T) {
func TestGroupsSchema(t *testing.T) { func TestGroupsSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Groups() s := Groups().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentGroupsComments, mkDataSourceVirtualEnvironmentGroupsComments,

View File

@ -28,7 +28,7 @@ func TestHostsInstantiation(t *testing.T) {
func TestHostsSchema(t *testing.T) { func TestHostsSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Hosts() s := Hosts().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentHostsNodeName, mkDataSourceVirtualEnvironmentHostsNodeName,

View File

@ -28,7 +28,7 @@ func TestNodesInstantiation(t *testing.T) {
func TestNodesSchema(t *testing.T) { func TestNodesSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Nodes() s := Nodes().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentNodesCPUCount, mkDataSourceVirtualEnvironmentNodesCPUCount,

View File

@ -28,7 +28,7 @@ func TestPoolInstantiation(t *testing.T) {
func TestPoolSchema(t *testing.T) { func TestPoolSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Pool() s := Pool().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentPoolPoolID, mkDataSourceVirtualEnvironmentPoolPoolID,

View File

@ -28,7 +28,7 @@ func TestPoolsInstantiation(t *testing.T) {
func TestPoolsSchema(t *testing.T) { func TestPoolsSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Pools() s := Pools().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentPoolsPoolIDs, mkDataSourceVirtualEnvironmentPoolsPoolIDs,

View File

@ -28,7 +28,7 @@ func TestRoleInstantiation(t *testing.T) {
func TestRoleSchema(t *testing.T) { func TestRoleSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Role() s := Role().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentRoleID, mkDataSourceVirtualEnvironmentRoleID,

View File

@ -28,7 +28,7 @@ func TestRolesInstantiation(t *testing.T) {
func TestRolesSchema(t *testing.T) { func TestRolesSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Roles() s := Roles().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentRolesPrivileges, mkDataSourceVirtualEnvironmentRolesPrivileges,

View File

@ -28,7 +28,7 @@ func TestTimeInstantiation(t *testing.T) {
func TestTimeSchema(t *testing.T) { func TestTimeSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Time() s := Time().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentTimeNodeName, mkDataSourceVirtualEnvironmentTimeNodeName,

View File

@ -28,7 +28,7 @@ func TestUserInstantiation(t *testing.T) {
func TestUserSchema(t *testing.T) { func TestUserSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := User() s := User().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkDataSourceVirtualEnvironmentUserUserID, mkDataSourceVirtualEnvironmentUserUserID,

View File

@ -28,7 +28,7 @@ func TestUsersInstantiation(t *testing.T) {
func TestUsersSchema(t *testing.T) { func TestUsersSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Users() s := Users().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentUsersComments, mkDataSourceVirtualEnvironmentUsersComments,

View File

@ -29,7 +29,7 @@ func TestVMInstantiation(t *testing.T) {
func TestVMSchema(t *testing.T) { func TestVMSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := VM() s := VM().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentVMName, mkDataSourceVirtualEnvironmentVMName,

View File

@ -29,7 +29,7 @@ func TestVMsInstantiation(t *testing.T) {
func TestVMsSchema(t *testing.T) { func TestVMsSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := VMs() s := VMs().Schema
test.AssertComputedAttributes(t, s, []string{ test.AssertComputedAttributes(t, s, []string{
mkDataSourceVirtualEnvironmentVMs, mkDataSourceVirtualEnvironmentVMs,

View File

@ -28,9 +28,7 @@ func TestProviderInstantiation(t *testing.T) {
func TestProviderSchema(t *testing.T) { func TestProviderSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := &schema.Resource{ s := ProxmoxVirtualEnvironment().Schema
Schema: ProxmoxVirtualEnvironment().Schema,
}
test.AssertOptionalArguments(t, s, []string{ test.AssertOptionalArguments(t, s, []string{
mkProviderUsername, mkProviderUsername,

View File

@ -28,7 +28,7 @@ func TestCertificateInstantiation(t *testing.T) {
func TestCertificateSchema(t *testing.T) { func TestCertificateSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Certificate() s := Certificate().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentCertificateCertificate, mkResourceVirtualEnvironmentCertificateCertificate,

View File

@ -26,7 +26,7 @@ func TestSecurityGroupInstantiation(t *testing.T) {
func TestSecurityGroupSchema(t *testing.T) { func TestSecurityGroupSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := SecurityGroup() s := SecurityGroup().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkSecurityGroupName, mkSecurityGroupName,

View File

@ -28,7 +28,7 @@ func TestContainerInstantiation(t *testing.T) {
func TestContainerSchema(t *testing.T) { func TestContainerSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Container() s := Container().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkNodeName, mkNodeName,

View File

@ -28,7 +28,7 @@ func TestDNSInstantiation(t *testing.T) {
func TestDNSSchema(t *testing.T) { func TestDNSSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := DNS() s := DNS().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentDNSDomain, mkResourceVirtualEnvironmentDNSDomain,

View File

@ -29,7 +29,7 @@ func TestFileInstantiation(t *testing.T) {
func TestFileSchema(t *testing.T) { func TestFileSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := File() s := File().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentFileDatastoreID, mkResourceVirtualEnvironmentFileDatastoreID,

View File

@ -25,7 +25,7 @@ func TestAliasInstantiation(t *testing.T) {
func TestAliasSchema(t *testing.T) { func TestAliasSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Alias() s := Alias().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkAliasName, mkAliasName,

View File

@ -26,7 +26,7 @@ func TestIPSetInstantiation(t *testing.T) {
func TestIPSetSchema(t *testing.T) { func TestIPSetSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := IPSet() s := IPSet().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkIPSetName, mkIPSetName,

View File

@ -25,7 +25,7 @@ func TestOptionsInstantiation(t *testing.T) {
func TestOptionsSchema(t *testing.T) { func TestOptionsSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Options() s := Options().Schema
test.AssertOptionalArguments(t, s, []string{ test.AssertOptionalArguments(t, s, []string{
mkDHCP, mkDHCP,

View File

@ -25,7 +25,7 @@ func TestRuleInstantiation(t *testing.T) {
func TestRuleSchema(t *testing.T) { func TestRuleSchema(t *testing.T) {
t.Parallel() t.Parallel()
rules := Rules() rules := Rules().Schema
test.AssertRequiredArguments(t, rules, []string{ test.AssertRequiredArguments(t, rules, []string{
MkRule, MkRule,

View File

@ -28,7 +28,7 @@ func TestGroupInstantiation(t *testing.T) {
func TestGroupSchema(t *testing.T) { func TestGroupSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Group() s := Group().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentGroupID, mkResourceVirtualEnvironmentGroupID,

View File

@ -28,7 +28,7 @@ func TestHostsInstantiation(t *testing.T) {
func TestHostsSchema(t *testing.T) { func TestHostsSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Hosts() s := Hosts().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentHostsEntry, mkResourceVirtualEnvironmentHostsEntry,

View File

@ -28,7 +28,7 @@ func TestPoolInstantiation(t *testing.T) {
func TestPoolSchema(t *testing.T) { func TestPoolSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Pool() s := Pool().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentPoolPoolID, mkResourceVirtualEnvironmentPoolPoolID,

View File

@ -28,7 +28,7 @@ func TestRoleInstantiation(t *testing.T) {
func TestRoleSchema(t *testing.T) { func TestRoleSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Role() s := Role().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentRolePrivileges, mkResourceVirtualEnvironmentRolePrivileges,

View File

@ -28,7 +28,7 @@ func TestTimeInstantiation(t *testing.T) {
func TestTimeSchema(t *testing.T) { func TestTimeSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := Time() s := Time().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentTimeNodeName, mkResourceVirtualEnvironmentTimeNodeName,

View File

@ -28,7 +28,7 @@ func TestUserInstantiation(t *testing.T) {
func TestUserSchema(t *testing.T) { func TestUserSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := User() s := User().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentUserUserID, mkResourceVirtualEnvironmentUserUserID,

View File

@ -0,0 +1,53 @@
package disk
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
)
func TestVMSchema(t *testing.T) {
t.Parallel()
s := Schema()
diskSchema := test.AssertNestedSchemaExistence(t, s, MkDisk)
test.AssertOptionalArguments(t, diskSchema, []string{
mkDiskDatastoreID,
mkDiskPathInDatastore,
mkDiskFileFormat,
mkDiskFileID,
mkDiskSize,
})
test.AssertValueTypes(t, diskSchema, map[string]schema.ValueType{
mkDiskDatastoreID: schema.TypeString,
mkDiskPathInDatastore: schema.TypeString,
mkDiskFileFormat: schema.TypeString,
mkDiskFileID: schema.TypeString,
mkDiskSize: schema.TypeInt,
})
diskSpeedSchema := test.AssertNestedSchemaExistence(
t,
diskSchema,
mkDiskSpeed,
)
test.AssertOptionalArguments(t, diskSpeedSchema, []string{
mkDiskSpeedRead,
mkDiskSpeedReadBurstable,
mkDiskSpeedWrite,
mkDiskSpeedWriteBurstable,
})
test.AssertValueTypes(t, diskSpeedSchema, map[string]schema.ValueType{
mkDiskSpeedRead: schema.TypeInt,
mkDiskSpeedReadBurstable: schema.TypeInt,
mkDiskSpeedWrite: schema.TypeInt,
mkDiskSpeedWriteBurstable: schema.TypeInt,
})
}

View File

@ -0,0 +1,276 @@
package network
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)
// GetNetworkDeviceObjects returns a list of network devices from the resource data.
func GetNetworkDeviceObjects(d *schema.ResourceData) (vms.CustomNetworkDevices, error) {
networkDevice := d.Get(MkNetworkDevice).([]interface{})
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))
for i, networkDeviceEntry := range networkDevice {
block := networkDeviceEntry.(map[string]interface{})
bridge := block[mkNetworkDeviceBridge].(string)
enabled := block[mkNetworkDeviceEnabled].(bool)
firewall := types.CustomBool(block[mkNetworkDeviceFirewall].(bool))
macAddress := block[mkNetworkDeviceMACAddress].(string)
model := block[mkNetworkDeviceModel].(string)
queues := block[mkNetworkDeviceQueues].(int)
rateLimit := block[mkNetworkDeviceRateLimit].(float64)
vlanID := block[mkNetworkDeviceVLANID].(int)
trunks := block[mkNetworkDeviceTrunks].(string)
mtu := block[mkNetworkDeviceMTU].(int)
device := vms.CustomNetworkDevice{
Enabled: enabled,
Firewall: &firewall,
Model: model,
}
if bridge != "" {
device.Bridge = &bridge
}
if macAddress != "" {
device.MACAddress = &macAddress
}
if queues != 0 {
device.Queues = &queues
}
if rateLimit != 0 {
device.RateLimit = &rateLimit
}
if vlanID != 0 {
device.Tag = &vlanID
}
if trunks != "" {
splitTrunks := strings.Split(trunks, ";")
var trunksAsInt []int
for _, numStr := range splitTrunks {
num, err := strconv.Atoi(numStr)
if err != nil {
return nil, fmt.Errorf("error parsing trunks: %w", err)
}
trunksAsInt = append(trunksAsInt, num)
}
device.Trunks = trunksAsInt
}
if mtu != 0 {
device.MTU = &mtu
}
networkDeviceObjects[i] = device
}
return networkDeviceObjects, nil
}
// ReadNetworkDeviceObjects reads the network device objects from the resource data.
func ReadNetworkDeviceObjects(d *schema.ResourceData, vmConfig *vms.GetResponseData) diag.Diagnostics {
var diags diag.Diagnostics
// Compare the network devices to those stored in the state.
currentNetworkDeviceList := d.Get(MkNetworkDevice).([]interface{})
macAddresses := make([]interface{}, MaxNetworkDevices)
networkDeviceLast := -1
networkDeviceList := make([]interface{}, MaxNetworkDevices)
networkDeviceObjects := []*vms.CustomNetworkDevice{
vmConfig.NetworkDevice0,
vmConfig.NetworkDevice1,
vmConfig.NetworkDevice2,
vmConfig.NetworkDevice3,
vmConfig.NetworkDevice4,
vmConfig.NetworkDevice5,
vmConfig.NetworkDevice6,
vmConfig.NetworkDevice7,
vmConfig.NetworkDevice8,
vmConfig.NetworkDevice9,
vmConfig.NetworkDevice10,
vmConfig.NetworkDevice11,
vmConfig.NetworkDevice12,
vmConfig.NetworkDevice13,
vmConfig.NetworkDevice14,
vmConfig.NetworkDevice15,
vmConfig.NetworkDevice16,
vmConfig.NetworkDevice17,
vmConfig.NetworkDevice18,
vmConfig.NetworkDevice19,
vmConfig.NetworkDevice20,
vmConfig.NetworkDevice21,
vmConfig.NetworkDevice22,
vmConfig.NetworkDevice23,
vmConfig.NetworkDevice24,
vmConfig.NetworkDevice25,
vmConfig.NetworkDevice26,
vmConfig.NetworkDevice27,
vmConfig.NetworkDevice28,
vmConfig.NetworkDevice29,
vmConfig.NetworkDevice30,
vmConfig.NetworkDevice31,
}
for ni, nd := range networkDeviceObjects {
networkDevice := map[string]interface{}{}
if nd != nil {
networkDeviceLast = ni
if nd.Bridge != nil {
networkDevice[mkNetworkDeviceBridge] = *nd.Bridge
} else {
networkDevice[mkNetworkDeviceBridge] = ""
}
networkDevice[mkNetworkDeviceEnabled] = nd.Enabled
if nd.Firewall != nil {
networkDevice[mkNetworkDeviceFirewall] = *nd.Firewall
} else {
networkDevice[mkNetworkDeviceFirewall] = false
}
if nd.MACAddress != nil {
macAddresses[ni] = *nd.MACAddress
} else {
macAddresses[ni] = ""
}
networkDevice[mkNetworkDeviceMACAddress] = macAddresses[ni]
networkDevice[mkNetworkDeviceModel] = nd.Model
if nd.Queues != nil {
networkDevice[mkNetworkDeviceQueues] = *nd.Queues
} else {
networkDevice[mkNetworkDeviceQueues] = 0
}
if nd.RateLimit != nil {
networkDevice[mkNetworkDeviceRateLimit] = *nd.RateLimit
} else {
networkDevice[mkNetworkDeviceRateLimit] = 0
}
if nd.Tag != nil {
networkDevice[mkNetworkDeviceVLANID] = nd.Tag
} else {
networkDevice[mkNetworkDeviceVLANID] = 0
}
if nd.Trunks != nil {
networkDevice[mkNetworkDeviceTrunks] = strings.Trim(
strings.Join(strings.Fields(fmt.Sprint(nd.Trunks)), ";"), "[]")
} else {
networkDevice[mkNetworkDeviceTrunks] = ""
}
if nd.MTU != nil {
networkDevice[mkNetworkDeviceMTU] = nd.MTU
} else {
networkDevice[mkNetworkDeviceMTU] = 0
}
} else {
macAddresses[ni] = ""
networkDevice[mkNetworkDeviceEnabled] = false
}
networkDeviceList[ni] = networkDevice
}
if len(currentNetworkDeviceList) > 0 || networkDeviceLast > -1 {
err := d.Set(MkNetworkDevice, networkDeviceList[:networkDeviceLast+1])
diags = append(diags, diag.FromErr(err)...)
}
err := d.Set(mkMACAddresses, macAddresses[0:len(currentNetworkDeviceList)])
diags = append(diags, diag.FromErr(err)...)
return diags
}
// ReadNetworkValues reads the network values from the resource data.
func ReadNetworkValues(
ctx context.Context,
d *schema.ResourceData,
vmAPI *vms.Client,
started bool,
vmConfig *vms.GetResponseData,
agentTimeout time.Duration,
) diag.Diagnostics {
var diags diag.Diagnostics
var ipv4Addresses []interface{}
var ipv6Addresses []interface{}
var networkInterfaceNames []interface{}
if started {
if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled {
var macAddresses []interface{}
networkInterfaces, err := vmAPI.WaitForNetworkInterfacesFromVMAgent(ctx, int(agentTimeout.Seconds()), 5, true)
if err == nil && networkInterfaces.Result != nil {
ipv4Addresses = make([]interface{}, len(*networkInterfaces.Result))
ipv6Addresses = make([]interface{}, len(*networkInterfaces.Result))
macAddresses = make([]interface{}, len(*networkInterfaces.Result))
networkInterfaceNames = make([]interface{}, len(*networkInterfaces.Result))
for ri, rv := range *networkInterfaces.Result {
var rvIPv4Addresses []interface{}
var rvIPv6Addresses []interface{}
if rv.IPAddresses != nil {
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
macAddresses[ri] = strings.ToUpper(rv.MACAddress)
networkInterfaceNames[ri] = rv.Name
}
}
err = d.Set(mkMACAddresses, macAddresses)
diags = append(diags, diag.FromErr(err)...)
}
}
e := d.Set(mkIPv4Addresses, ipv4Addresses)
diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkIPv6Addresses, ipv6Addresses)
diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkNetworkInterfaceNames, networkInterfaceNames)
diags = append(diags, diag.FromErr(e)...)
return diags
}

View File

@ -0,0 +1,189 @@
package network
import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validators"
)
const (
// MaxNetworkDevices is the maximum number of network devices supported by the resource.
MaxNetworkDevices = 32
dvNetworkDeviceBridge = "vmbr0"
dvNetworkDeviceEnabled = true
dvNetworkDeviceFirewall = false
dvNetworkDeviceModel = "virtio"
dvNetworkDeviceQueues = 0
dvNetworkDeviceRateLimit = 0
dvNetworkDeviceVLANID = 0
dvNetworkDeviceTrunks = ""
dvNetworkDeviceMTU = 0
mkIPv4Addresses = "ipv4_addresses"
mkIPv6Addresses = "ipv6_addresses"
mkMACAddresses = "mac_addresses"
// MkNetworkDevice is the name of the network device.
MkNetworkDevice = "network_device"
mkNetworkDeviceBridge = "bridge"
mkNetworkDeviceEnabled = "enabled"
mkNetworkDeviceFirewall = "firewall"
mkNetworkDeviceMACAddress = "mac_address"
mkNetworkDeviceModel = "model"
mkNetworkDeviceQueues = "queues"
mkNetworkDeviceRateLimit = "rate_limit"
mkNetworkDeviceVLANID = "vlan_id"
mkNetworkDeviceTrunks = "trunks"
mkNetworkDeviceMTU = "mtu"
mkNetworkInterfaceNames = "network_interface_names"
)
// Schema returns the schema for the network resource.
func Schema() map[string]*schema.Schema {
return map[string]*schema.Schema{
mkIPv4Addresses: {
Type: schema.TypeList,
Description: "The IPv4 addresses published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
mkIPv6Addresses: {
Type: schema.TypeList,
Description: "The IPv6 addresses published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
mkMACAddresses: {
Type: schema.TypeList,
Description: "The MAC addresses for the network interfaces",
Computed: true,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
MkNetworkDevice: {
Type: schema.TypeList,
Description: "The network devices",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return make([]interface{}, 1), nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkNetworkDeviceBridge: {
Type: schema.TypeString,
Description: "The bridge",
Optional: true,
Default: dvNetworkDeviceBridge,
},
mkNetworkDeviceEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the network device",
Optional: true,
Default: dvNetworkDeviceEnabled,
},
mkNetworkDeviceFirewall: {
Type: schema.TypeBool,
Description: "Whether this interface's firewall rules should be used",
Optional: true,
Default: dvNetworkDeviceFirewall,
},
mkNetworkDeviceMACAddress: {
Type: schema.TypeString,
Description: "The MAC address",
Optional: true,
Computed: true,
ValidateDiagFunc: validators.MACAddress(),
},
mkNetworkDeviceModel: {
Type: schema.TypeString,
Description: "The model",
Optional: true,
Default: dvNetworkDeviceModel,
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{
"e1000",
"rtl8139",
"virtio",
"vmxnet3",
}, false)),
},
mkNetworkDeviceQueues: {
Type: schema.TypeInt,
Description: "Number of packet queues to be used on the device",
Optional: true,
Default: dvNetworkDeviceQueues,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 64)),
},
mkNetworkDeviceRateLimit: {
Type: schema.TypeFloat,
Description: "The rate limit in megabytes per second",
Optional: true,
Default: dvNetworkDeviceRateLimit,
},
mkNetworkDeviceVLANID: {
Type: schema.TypeInt,
Description: "The VLAN identifier",
Optional: true,
Default: dvNetworkDeviceVLANID,
},
mkNetworkDeviceTrunks: {
Type: schema.TypeString,
Optional: true,
Description: "List of VLAN trunks for the network interface",
},
mkNetworkDeviceMTU: {
Type: schema.TypeInt,
Description: "Maximum transmission unit (MTU)",
Optional: true,
Default: dvNetworkDeviceMTU,
},
},
},
MaxItems: MaxNetworkDevices,
MinItems: 0,
},
mkNetworkInterfaceNames: {
Type: schema.TypeList,
Description: "The network interface names published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
}
}
// CustomizeDiff returns the custom diff functions for the network resource.
func CustomizeDiff() []schema.CustomizeDiffFunc {
return []schema.CustomizeDiffFunc{
customdiff.ComputedIf(
mkIPv4Addresses,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
return d.HasChange("started") ||
d.HasChange(MkNetworkDevice)
},
),
customdiff.ComputedIf(
mkIPv6Addresses,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
return d.HasChange("started") ||
d.HasChange(MkNetworkDevice)
},
),
customdiff.ComputedIf(
mkNetworkInterfaceNames,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
return d.HasChange("started") ||
d.HasChange(MkNetworkDevice)
},
),
}
}

View File

@ -0,0 +1,55 @@
package network
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
)
func TestNetworkSchema(t *testing.T) {
t.Parallel()
s := Schema()
test.AssertComputedAttributes(t, s, []string{
mkIPv4Addresses,
mkIPv6Addresses,
mkMACAddresses,
mkNetworkInterfaceNames,
})
test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkIPv4Addresses: schema.TypeList,
mkIPv6Addresses: schema.TypeList,
mkMACAddresses: schema.TypeList,
mkNetworkInterfaceNames: schema.TypeList,
})
deviceSchema := test.AssertNestedSchemaExistence(
t,
s,
MkNetworkDevice,
)
test.AssertOptionalArguments(t, deviceSchema, []string{
mkNetworkDeviceBridge,
mkNetworkDeviceEnabled,
mkNetworkDeviceMACAddress,
mkNetworkDeviceModel,
mkNetworkDeviceRateLimit,
mkNetworkDeviceVLANID,
mkNetworkDeviceMTU,
})
test.AssertValueTypes(t, deviceSchema, map[string]schema.ValueType{
mkNetworkDeviceBridge: schema.TypeString,
mkNetworkDeviceEnabled: schema.TypeBool,
mkNetworkDeviceMACAddress: schema.TypeString,
mkNetworkDeviceModel: schema.TypeString,
mkNetworkDeviceRateLimit: schema.TypeFloat,
mkNetworkDeviceVLANID: schema.TypeInt,
mkNetworkDeviceMTU: schema.TypeInt,
})
}

View File

@ -144,11 +144,6 @@ func CPUTypeValidator() schema.SchemaValidateDiagFunc {
)) ))
} }
// NetworkDeviceModelValidator is a schema validation function for network device models.
func NetworkDeviceModelValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{"e1000", "rtl8139", "virtio", "vmxnet3"}, false))
}
// QEMUAgentTypeValidator is a schema validation function for QEMU agent types. // QEMUAgentTypeValidator is a schema validation function for QEMU agent types.
func QEMUAgentTypeValidator() schema.SchemaValidateDiagFunc { func QEMUAgentTypeValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{"isa", "virtio"}, false)) return validation.ToDiagFunc(validation.StringInSlice([]string{"isa", "virtio"}, false))

View File

@ -17,6 +17,7 @@ import (
"time" "time"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/network"
"github.com/bpg/terraform-provider-proxmox/utils" "github.com/bpg/terraform-provider-proxmox/utils"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -93,48 +94,39 @@ const (
dvMemoryShared = 0 dvMemoryShared = 0
dvMigrate = false dvMigrate = false
dvName = "" dvName = ""
dvNetworkDeviceBridge = "vmbr0"
dvNetworkDeviceEnabled = true dvOperatingSystemType = "other"
dvNetworkDeviceFirewall = false dvPoolID = ""
dvNetworkDeviceModel = "virtio" dvProtection = false
dvNetworkDeviceQueues = 0 dvSerialDeviceDevice = "socket"
dvNetworkDeviceRateLimit = 0 dvSMBIOSFamily = ""
dvNetworkDeviceVLANID = 0 dvSMBIOSManufacturer = ""
dvNetworkDeviceTrunks = "" dvSMBIOSProduct = ""
dvNetworkDeviceMTU = 0 dvSMBIOSSKU = ""
dvOperatingSystemType = "other" dvSMBIOSSerial = ""
dvPoolID = "" dvSMBIOSVersion = ""
dvProtection = false dvStarted = true
dvSerialDeviceDevice = "socket" dvStartupOrder = -1
dvSMBIOSFamily = "" dvStartupUpDelay = -1
dvSMBIOSManufacturer = "" dvStartupDownDelay = -1
dvSMBIOSProduct = "" dvTabletDevice = true
dvSMBIOSSKU = "" dvTemplate = false
dvSMBIOSSerial = "" dvTimeoutClone = 1800
dvSMBIOSVersion = "" dvTimeoutCreate = 1800
dvStarted = true dvTimeoutMoveDisk = 1800
dvStartupOrder = -1 dvTimeoutMigrate = 1800
dvStartupUpDelay = -1 dvTimeoutReboot = 1800
dvStartupDownDelay = -1 dvTimeoutShutdownVM = 1800
dvTabletDevice = true dvTimeoutStartVM = 1800
dvTemplate = false dvTimeoutStopVM = 300
dvTimeoutClone = 1800 dvVGAEnabled = true
dvTimeoutCreate = 1800 dvVGAMemory = 16
dvTimeoutMoveDisk = 1800 dvVGAType = "std"
dvTimeoutMigrate = 1800 dvSCSIHardware = "virtio-scsi-pci"
dvTimeoutReboot = 1800 dvStopOnDestroy = false
dvTimeoutShutdownVM = 1800 dvHookScript = ""
dvTimeoutStartVM = 1800
dvTimeoutStopVM = 300
dvVGAEnabled = true
dvVGAMemory = 16
dvVGAType = "std"
dvSCSIHardware = "virtio-scsi-pci"
dvStopOnDestroy = false
dvHookScript = ""
maxResourceVirtualEnvironmentVMAudioDevices = 1 maxResourceVirtualEnvironmentVMAudioDevices = 1
maxResourceVirtualEnvironmentVMNetworkDevices = 32
maxResourceVirtualEnvironmentVMSerialDevices = 4 maxResourceVirtualEnvironmentVMSerialDevices = 4
maxResourceVirtualEnvironmentVMHostPCIDevices = 8 maxResourceVirtualEnvironmentVMHostPCIDevices = 8
maxResourceVirtualEnvironmentVMHostUSBDevices = 4 maxResourceVirtualEnvironmentVMHostUSBDevices = 4
@ -215,72 +207,59 @@ const (
mkInitializationVendorDataFileID = "vendor_data_file_id" mkInitializationVendorDataFileID = "vendor_data_file_id"
mkInitializationNetworkDataFileID = "network_data_file_id" mkInitializationNetworkDataFileID = "network_data_file_id"
mkInitializationMetaDataFileID = "meta_data_file_id" mkInitializationMetaDataFileID = "meta_data_file_id"
mkIPv4Addresses = "ipv4_addresses"
mkIPv6Addresses = "ipv6_addresses" mkKeyboardLayout = "keyboard_layout"
mkKeyboardLayout = "keyboard_layout" mkKVMArguments = "kvm_arguments"
mkKVMArguments = "kvm_arguments" mkMachine = "machine"
mkMachine = "machine" mkMemory = "memory"
mkMACAddresses = "mac_addresses" mkMemoryDedicated = "dedicated"
mkMemory = "memory" mkMemoryFloating = "floating"
mkMemoryDedicated = "dedicated" mkMemoryShared = "shared"
mkMemoryFloating = "floating" mkMigrate = "migrate"
mkMemoryShared = "shared" mkName = "name"
mkMigrate = "migrate"
mkName = "name" mkNodeName = "node_name"
mkNetworkDevice = "network_device" mkOperatingSystem = "operating_system"
mkNetworkDeviceBridge = "bridge" mkOperatingSystemType = "type"
mkNetworkDeviceEnabled = "enabled" mkPoolID = "pool_id"
mkNetworkDeviceFirewall = "firewall" mkProtection = "protection"
mkNetworkDeviceMACAddress = "mac_address" mkSerialDevice = "serial_device"
mkNetworkDeviceModel = "model" mkSerialDeviceDevice = "device"
mkNetworkDeviceQueues = "queues" mkSMBIOS = "smbios"
mkNetworkDeviceRateLimit = "rate_limit" mkSMBIOSFamily = "family"
mkNetworkDeviceVLANID = "vlan_id" mkSMBIOSManufacturer = "manufacturer"
mkNetworkDeviceTrunks = "trunks" mkSMBIOSProduct = "product"
mkNetworkDeviceMTU = "mtu" mkSMBIOSSKU = "sku"
mkNetworkInterfaceNames = "network_interface_names" mkSMBIOSSerial = "serial"
mkNodeName = "node_name" mkSMBIOSUUID = "uuid"
mkOperatingSystem = "operating_system" mkSMBIOSVersion = "version"
mkOperatingSystemType = "type" mkStarted = "started"
mkPoolID = "pool_id" mkStartup = "startup"
mkProtection = "protection" mkStartupOrder = "order"
mkSerialDevice = "serial_device" mkStartupUpDelay = "up_delay"
mkSerialDeviceDevice = "device" mkStartupDownDelay = "down_delay"
mkSMBIOS = "smbios" mkTabletDevice = "tablet_device"
mkSMBIOSFamily = "family" mkTags = "tags"
mkSMBIOSManufacturer = "manufacturer" mkTemplate = "template"
mkSMBIOSProduct = "product" mkTimeoutClone = "timeout_clone"
mkSMBIOSSKU = "sku" mkTimeoutCreate = "timeout_create"
mkSMBIOSSerial = "serial" mkTimeoutMigrate = "timeout_migrate"
mkSMBIOSUUID = "uuid" mkTimeoutReboot = "timeout_reboot"
mkSMBIOSVersion = "version" mkTimeoutShutdownVM = "timeout_shutdown_vm"
mkStarted = "started" mkTimeoutStartVM = "timeout_start_vm"
mkStartup = "startup" mkTimeoutStopVM = "timeout_stop_vm"
mkStartupOrder = "order" mkHostUSB = "usb"
mkStartupUpDelay = "up_delay" mkHostUSBDevice = "host"
mkStartupDownDelay = "down_delay" mkHostUSBDeviceMapping = "mapping"
mkTabletDevice = "tablet_device" mkHostUSBDeviceUSB3 = "usb3"
mkTags = "tags" mkVGA = "vga"
mkTemplate = "template" mkVGAEnabled = "enabled"
mkTimeoutClone = "timeout_clone" mkVGAMemory = "memory"
mkTimeoutCreate = "timeout_create" mkVGAType = "type"
mkTimeoutMigrate = "timeout_migrate" mkVMID = "vm_id"
mkTimeoutReboot = "timeout_reboot" mkSCSIHardware = "scsi_hardware"
mkTimeoutShutdownVM = "timeout_shutdown_vm" mkHookScriptFileID = "hook_script_file_id"
mkTimeoutStartVM = "timeout_start_vm" mkStopOnDestroy = "stop_on_destroy"
mkTimeoutStopVM = "timeout_stop_vm"
mkHostUSB = "usb"
mkHostUSBDevice = "host"
mkHostUSBDeviceMapping = "mapping"
mkHostUSBDeviceUSB3 = "usb3"
mkVGA = "vga"
mkVGAEnabled = "enabled"
mkVGAMemory = "memory"
mkVGAType = "type"
mkVMID = "vm_id"
mkSCSIHardware = "scsi_hardware"
mkHookScriptFileID = "hook_script_file_id"
mkStopOnDestroy = "stop_on_destroy"
) )
// VM returns a resource that manages VMs. // VM returns a resource that manages VMs.
@ -897,24 +876,6 @@ func VM() *schema.Resource {
MaxItems: 1, MaxItems: 1,
MinItems: 0, MinItems: 0,
}, },
mkIPv4Addresses: {
Type: schema.TypeList,
Description: "The IPv4 addresses published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
mkIPv6Addresses: {
Type: schema.TypeList,
Description: "The IPv6 addresses published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
mkHostPCI: { mkHostPCI: {
Type: schema.TypeList, Type: schema.TypeList,
Description: "The Host PCI devices mapped to the VM", Description: "The Host PCI devices mapped to the VM",
@ -1013,13 +974,6 @@ func VM() *schema.Resource {
Default: dvMachineType, Default: dvMachineType,
ValidateDiagFunc: MachineTypeValidator(), ValidateDiagFunc: MachineTypeValidator(),
}, },
mkMACAddresses: {
Type: schema.TypeList,
Description: "The MAC addresses for the network interfaces",
Computed: true,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkMemory: { mkMemory: {
Type: schema.TypeList, Type: schema.TypeList,
Description: "The memory allocation", Description: "The memory allocation",
@ -1073,88 +1027,6 @@ func VM() *schema.Resource {
Optional: true, Optional: true,
Default: dvName, Default: dvName,
}, },
mkNetworkDevice: {
Type: schema.TypeList,
Description: "The network devices",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return make([]interface{}, 1), nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkNetworkDeviceBridge: {
Type: schema.TypeString,
Description: "The bridge",
Optional: true,
Default: dvNetworkDeviceBridge,
},
mkNetworkDeviceEnabled: {
Type: schema.TypeBool,
Description: "Whether to enable the network device",
Optional: true,
Default: dvNetworkDeviceEnabled,
},
mkNetworkDeviceFirewall: {
Type: schema.TypeBool,
Description: "Whether this interface's firewall rules should be used",
Optional: true,
Default: dvNetworkDeviceFirewall,
},
mkNetworkDeviceMACAddress: {
Type: schema.TypeString,
Description: "The MAC address",
Optional: true,
Computed: true,
ValidateDiagFunc: validators.MACAddress(),
},
mkNetworkDeviceModel: {
Type: schema.TypeString,
Description: "The model",
Optional: true,
Default: dvNetworkDeviceModel,
ValidateDiagFunc: NetworkDeviceModelValidator(),
},
mkNetworkDeviceQueues: {
Type: schema.TypeInt,
Description: "Number of packet queues to be used on the device",
Optional: true,
Default: dvNetworkDeviceQueues,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 64)),
},
mkNetworkDeviceRateLimit: {
Type: schema.TypeFloat,
Description: "The rate limit in megabytes per second",
Optional: true,
Default: dvNetworkDeviceRateLimit,
},
mkNetworkDeviceVLANID: {
Type: schema.TypeInt,
Description: "The VLAN identifier",
Optional: true,
Default: dvNetworkDeviceVLANID,
},
mkNetworkDeviceTrunks: {
Type: schema.TypeString,
Optional: true,
Description: "List of VLAN trunks for the network interface",
},
mkNetworkDeviceMTU: {
Type: schema.TypeInt,
Description: "Maximum transmission unit (MTU)",
Optional: true,
Default: dvNetworkDeviceMTU,
},
},
},
MaxItems: maxResourceVirtualEnvironmentVMNetworkDevices,
MinItems: 0,
},
mkNetworkInterfaceNames: {
Type: schema.TypeList,
Description: "The network interface names published by the QEMU agent",
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
mkNodeName: { mkNodeName: {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The node name", Description: "The node name",
@ -1462,6 +1334,7 @@ func VM() *schema.Resource {
} }
structure.MergeSchema(s, disk.Schema()) structure.MergeSchema(s, disk.Schema())
structure.MergeSchema(s, network.Schema())
return &schema.Resource{ return &schema.Resource{
Schema: s, Schema: s,
@ -1470,27 +1343,7 @@ func VM() *schema.Resource {
UpdateContext: vmUpdate, UpdateContext: vmUpdate,
DeleteContext: vmDelete, DeleteContext: vmDelete,
CustomizeDiff: customdiff.All( CustomizeDiff: customdiff.All(
customdiff.ComputedIf( customdiff.All(network.CustomizeDiff()...),
mkIPv4Addresses,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
return d.HasChange(mkStarted) ||
d.HasChange(mkNetworkDevice)
},
),
customdiff.ComputedIf(
mkIPv6Addresses,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
return d.HasChange(mkStarted) ||
d.HasChange(mkNetworkDevice)
},
),
customdiff.ComputedIf(
mkNetworkInterfaceNames,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
return d.HasChange(mkStarted) ||
d.HasChange(mkNetworkDevice)
},
),
customdiff.ForceNewIf( customdiff.ForceNewIf(
mkVMID, mkVMID,
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool { func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
@ -1825,7 +1678,6 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
hostUSB := d.Get(mkHostUSB).([]interface{}) hostUSB := d.Get(mkHostUSB).([]interface{})
keyboardLayout := d.Get(mkKeyboardLayout).(string) keyboardLayout := d.Get(mkKeyboardLayout).(string)
memory := d.Get(mkMemory).([]interface{}) memory := d.Get(mkMemory).([]interface{})
networkDevice := d.Get(mkNetworkDevice).([]interface{})
operatingSystem := d.Get(mkOperatingSystem).([]interface{}) operatingSystem := d.Get(mkOperatingSystem).([]interface{})
serialDevice := d.Get(mkSerialDevice).([]interface{}) serialDevice := d.Get(mkSerialDevice).([]interface{})
onBoot := types.CustomBool(d.Get(mkOnBoot).(bool)) onBoot := types.CustomBool(d.Get(mkOnBoot).(bool))
@ -2027,8 +1879,9 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
} }
} }
networkDevice := d.Get(network.MkNetworkDevice).([]interface{})
if len(networkDevice) > 0 { if len(networkDevice) > 0 {
updateBody.NetworkDevices, err = vmGetNetworkDeviceObjects(d) updateBody.NetworkDevices, err = network.GetNetworkDeviceObjects(d)
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
@ -2039,7 +1892,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
} }
} }
for i := len(updateBody.NetworkDevices); i < maxResourceVirtualEnvironmentVMNetworkDevices; i++ { for i := len(updateBody.NetworkDevices); i < network.MaxNetworkDevices; i++ {
del = append(del, fmt.Sprintf("net%d", i)) del = append(del, fmt.Sprintf("net%d", i))
} }
} }
@ -2426,7 +2279,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
name := d.Get(mkName).(string) name := d.Get(mkName).(string)
tags := d.Get(mkTags).([]interface{}) tags := d.Get(mkTags).([]interface{})
networkDeviceObjects, err := vmGetNetworkDeviceObjects(d) networkDeviceObjects, err := network.GetNetworkDeviceObjects(d)
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
@ -3054,77 +2907,6 @@ func vmGetHostUSBDeviceObjects(d *schema.ResourceData) vms.CustomUSBDevices {
return usbDeviceObjects return usbDeviceObjects
} }
func vmGetNetworkDeviceObjects(d *schema.ResourceData) (vms.CustomNetworkDevices, error) {
networkDevice := d.Get(mkNetworkDevice).([]interface{})
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))
for i, networkDeviceEntry := range networkDevice {
block := networkDeviceEntry.(map[string]interface{})
bridge := block[mkNetworkDeviceBridge].(string)
enabled := block[mkNetworkDeviceEnabled].(bool)
firewall := types.CustomBool(block[mkNetworkDeviceFirewall].(bool))
macAddress := block[mkNetworkDeviceMACAddress].(string)
model := block[mkNetworkDeviceModel].(string)
queues := block[mkNetworkDeviceQueues].(int)
rateLimit := block[mkNetworkDeviceRateLimit].(float64)
vlanID := block[mkNetworkDeviceVLANID].(int)
trunks := block[mkNetworkDeviceTrunks].(string)
mtu := block[mkNetworkDeviceMTU].(int)
device := vms.CustomNetworkDevice{
Enabled: enabled,
Firewall: &firewall,
Model: model,
}
if bridge != "" {
device.Bridge = &bridge
}
if macAddress != "" {
device.MACAddress = &macAddress
}
if queues != 0 {
device.Queues = &queues
}
if rateLimit != 0 {
device.RateLimit = &rateLimit
}
if vlanID != 0 {
device.Tag = &vlanID
}
if trunks != "" {
splitTrunks := strings.Split(trunks, ";")
var trunksAsInt []int
for _, numStr := range splitTrunks {
num, err := strconv.Atoi(numStr)
if err != nil {
return nil, fmt.Errorf("error parsing trunks: %w", err)
}
trunksAsInt = append(trunksAsInt, num)
}
device.Trunks = trunksAsInt
}
if mtu != 0 {
device.MTU = &mtu
}
networkDeviceObjects[i] = device
}
return networkDeviceObjects, nil
}
func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices { func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices {
device := d.Get(mkSerialDevice).([]interface{}) device := d.Get(mkSerialDevice).([]interface{})
list := make(vms.CustomSerialDevices, len(device)) list := make(vms.CustomSerialDevices, len(device))
@ -3350,9 +3132,9 @@ func vmReadCustom(
) diag.Diagnostics { ) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration) config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient() api, e := config.GetClient()
if err != nil { if e != nil {
return diag.FromErr(err) return diag.FromErr(e)
} }
diags := vmReadPrimitiveValues(d, vmConfig, vmStatus) diags := vmReadPrimitiveValues(d, vmConfig, vmStatus)
@ -3368,7 +3150,7 @@ func vmReadCustom(
d.Id(), storedVMID, vmID), d.Id(), storedVMID, vmID),
}) })
err = d.Set(mkVMID, vmID) err := d.Set(mkVMID, vmID)
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -3418,7 +3200,7 @@ func vmReadCustom(
if len(clone) > 0 { if len(clone) > 0 {
if len(currentAgent) > 0 { if len(currentAgent) > 0 {
err = d.Set(mkAgent, []interface{}{agent}) err := d.Set(mkAgent, []interface{}{agent})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else if len(currentAgent) > 0 || } else if len(currentAgent) > 0 ||
@ -3426,16 +3208,16 @@ func vmReadCustom(
agent[mkAgentTimeout] != dvAgentTimeout || agent[mkAgentTimeout] != dvAgentTimeout ||
agent[mkAgentTrim] != dvAgentTrim || agent[mkAgentTrim] != dvAgentTrim ||
agent[mkAgentType] != dvAgentType { agent[mkAgentType] != dvAgentType {
err = d.Set(mkAgent, []interface{}{agent}) err := d.Set(mkAgent, []interface{}{agent})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else if len(clone) > 0 { } else if len(clone) > 0 {
if len(currentAgent) > 0 { if len(currentAgent) > 0 {
err = d.Set(mkAgent, []interface{}{}) err := d.Set(mkAgent, []interface{}{})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else { } else {
err = d.Set(mkAgent, []interface{}{}) err := d.Set(mkAgent, []interface{}{})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} }
@ -3474,7 +3256,7 @@ func vmReadCustom(
} }
if len(clone) == 0 || len(currentAudioDevice) > 0 { if len(clone) == 0 || len(currentAudioDevice) > 0 {
err = d.Set(mkAudioDevice, audioDevices[:audioDevicesCount]) err := d.Set(mkAudioDevice, audioDevices[:audioDevicesCount])
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -3512,11 +3294,11 @@ func vmReadCustom(
cdrom[0] = cdromBlock cdrom[0] = cdromBlock
err = d.Set(mkCDROM, cdrom) err := d.Set(mkCDROM, cdrom)
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else { } else {
err = d.Set(mkCDROM, []interface{}{}) err := d.Set(mkCDROM, []interface{}{})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -3601,7 +3383,7 @@ func vmReadCustom(
if len(clone) > 0 { if len(clone) > 0 {
if len(currentCPU) > 0 { if len(currentCPU) > 0 {
err = d.Set(mkCPU, []interface{}{cpu}) err := d.Set(mkCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else if len(currentCPU) > 0 || } else if len(currentCPU) > 0 ||
@ -3613,7 +3395,7 @@ func vmReadCustom(
cpu[mkCPUSockets] != dvCPUSockets || cpu[mkCPUSockets] != dvCPUSockets ||
cpu[mkCPUType] != dvCPUType || cpu[mkCPUType] != dvCPUType ||
cpu[mkCPUUnits] != dvCPUUnits { cpu[mkCPUUnits] != dvCPUUnits {
err = d.Set(mkCPU, []interface{}{cpu}) err := d.Set(mkCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -3636,8 +3418,8 @@ func vmReadCustom(
} else { } else {
// disk format may not be returned by config API if it is default for the storage, and that may be different // disk format may not be returned by config API if it is default for the storage, and that may be different
// from the default qcow2, so we need to read it from the storage API to make sure we have the correct value // from the default qcow2, so we need to read it from the storage API to make sure we have the correct value
volume, e := api.Node(nodeName).Storage(fileIDParts[0]).GetDatastoreFile(ctx, vmConfig.EFIDisk.FileVolume) volume, err := api.Node(nodeName).Storage(fileIDParts[0]).GetDatastoreFile(ctx, vmConfig.EFIDisk.FileVolume)
if e != nil { if err != nil {
diags = append(diags, diag.FromErr(e)...) diags = append(diags, diag.FromErr(e)...)
} else { } else {
efiDisk[mkEFIDiskFileFormat] = volume.FileFormat efiDisk[mkEFIDiskFileFormat] = volume.FileFormat
@ -3660,7 +3442,7 @@ func vmReadCustom(
if len(clone) > 0 { if len(clone) > 0 {
if len(currentEfiDisk) > 0 { if len(currentEfiDisk) > 0 {
err = d.Set(mkEFIDisk, []interface{}{efiDisk}) err := d.Set(mkEFIDisk, []interface{}{efiDisk})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else if len(currentEfiDisk) > 0 || } else if len(currentEfiDisk) > 0 ||
@ -3668,7 +3450,7 @@ func vmReadCustom(
efiDisk[mkEFIDiskType] != dvEFIDiskType || efiDisk[mkEFIDiskType] != dvEFIDiskType ||
efiDisk[mkEFIDiskPreEnrolledKeys] != dvEFIDiskPreEnrolledKeys || efiDisk[mkEFIDiskPreEnrolledKeys] != dvEFIDiskPreEnrolledKeys ||
efiDisk[mkEFIDiskFileFormat] != dvEFIDiskFileFormat { efiDisk[mkEFIDiskFileFormat] != dvEFIDiskFileFormat {
err = d.Set(mkEFIDisk, []interface{}{efiDisk}) err := d.Set(mkEFIDisk, []interface{}{efiDisk})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} }
@ -3685,13 +3467,13 @@ func vmReadCustom(
if len(clone) > 0 { if len(clone) > 0 {
if len(currentTPMState) > 0 { if len(currentTPMState) > 0 {
err = d.Set(mkTPMState, []interface{}{tpmState}) err := d.Set(mkTPMState, []interface{}{tpmState})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else if len(currentTPMState) > 0 || } else if len(currentTPMState) > 0 ||
tpmState[mkTPMStateDatastoreID] != dvTPMStateDatastoreID || tpmState[mkTPMStateDatastoreID] != dvTPMStateDatastoreID ||
tpmState[mkTPMStateVersion] != dvTPMStateVersion { tpmState[mkTPMStateVersion] != dvTPMStateVersion {
err = d.Set(mkTPMState, []interface{}{tpmState}) err := d.Set(mkTPMState, []interface{}{tpmState})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} }
@ -3755,7 +3537,7 @@ func vmReadCustom(
if len(clone) == 0 || len(currentPCIList) > 0 { if len(clone) == 0 || len(currentPCIList) > 0 {
orderedPCIList := utils.OrderedListFromMap(pciMap) orderedPCIList := utils.OrderedListFromMap(pciMap)
err = d.Set(mkHostPCI, orderedPCIList) err := d.Set(mkHostPCI, orderedPCIList)
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -3794,7 +3576,7 @@ func vmReadCustom(
if len(clone) == 0 || len(currentUSBList) > 0 { if len(clone) == 0 || len(currentUSBList) > 0 {
// NOTE: reordering of devices by PVE may cause an issue here // NOTE: reordering of devices by PVE may cause an issue here
orderedUSBList := utils.OrderedListFromMap(usbMap) orderedUSBList := utils.OrderedListFromMap(usbMap)
err = d.Set(mkHostUSB, orderedUSBList) err := d.Set(mkHostUSB, orderedUSBList)
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -4022,18 +3804,18 @@ func vmReadCustom(
if len(clone) > 0 { if len(clone) > 0 {
if len(currentInitialization) > 0 { if len(currentInitialization) > 0 {
if len(initialization) > 0 { if len(initialization) > 0 {
err = d.Set(mkInitialization, []interface{}{initialization}) err := d.Set(mkInitialization, []interface{}{initialization})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} else { } else {
err = d.Set(mkInitialization, []interface{}{}) err := d.Set(mkInitialization, []interface{}{})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} }
} else if len(initialization) > 0 { } else if len(initialization) > 0 {
err = d.Set(mkInitialization, []interface{}{initialization}) err := d.Set(mkInitialization, []interface{}{initialization})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} else { } else {
err = d.Set(mkInitialization, []interface{}{}) err := d.Set(mkInitialization, []interface{}{})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
@ -4071,132 +3853,18 @@ func vmReadCustom(
if len(clone) > 0 { if len(clone) > 0 {
if len(currentMemory) > 0 { if len(currentMemory) > 0 {
err = d.Set(mkMemory, []interface{}{memory}) err := d.Set(mkMemory, []interface{}{memory})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
} else if len(currentMemory) > 0 || } else if len(currentMemory) > 0 ||
memory[mkMemoryDedicated] != dvMemoryDedicated || memory[mkMemoryDedicated] != dvMemoryDedicated ||
memory[mkMemoryFloating] != dvMemoryFloating || memory[mkMemoryFloating] != dvMemoryFloating ||
memory[mkMemoryShared] != dvMemoryShared { memory[mkMemoryShared] != dvMemoryShared {
err = d.Set(mkMemory, []interface{}{memory}) err := d.Set(mkMemory, []interface{}{memory})
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
// Compare the network devices to those stored in the state. diags = append(diags, network.ReadNetworkDeviceObjects(d, vmConfig)...)
currentNetworkDeviceList := d.Get(mkNetworkDevice).([]interface{})
macAddresses := make([]interface{}, maxResourceVirtualEnvironmentVMNetworkDevices)
networkDeviceLast := -1
networkDeviceList := make([]interface{}, maxResourceVirtualEnvironmentVMNetworkDevices)
networkDeviceObjects := []*vms.CustomNetworkDevice{
vmConfig.NetworkDevice0,
vmConfig.NetworkDevice1,
vmConfig.NetworkDevice2,
vmConfig.NetworkDevice3,
vmConfig.NetworkDevice4,
vmConfig.NetworkDevice5,
vmConfig.NetworkDevice6,
vmConfig.NetworkDevice7,
vmConfig.NetworkDevice8,
vmConfig.NetworkDevice9,
vmConfig.NetworkDevice10,
vmConfig.NetworkDevice11,
vmConfig.NetworkDevice12,
vmConfig.NetworkDevice13,
vmConfig.NetworkDevice14,
vmConfig.NetworkDevice15,
vmConfig.NetworkDevice16,
vmConfig.NetworkDevice17,
vmConfig.NetworkDevice18,
vmConfig.NetworkDevice19,
vmConfig.NetworkDevice20,
vmConfig.NetworkDevice21,
vmConfig.NetworkDevice22,
vmConfig.NetworkDevice23,
vmConfig.NetworkDevice24,
vmConfig.NetworkDevice25,
vmConfig.NetworkDevice26,
vmConfig.NetworkDevice27,
vmConfig.NetworkDevice28,
vmConfig.NetworkDevice29,
vmConfig.NetworkDevice30,
vmConfig.NetworkDevice31,
}
for ni, nd := range networkDeviceObjects {
networkDevice := map[string]interface{}{}
if nd != nil {
networkDeviceLast = ni
if nd.Bridge != nil {
networkDevice[mkNetworkDeviceBridge] = *nd.Bridge
} else {
networkDevice[mkNetworkDeviceBridge] = ""
}
networkDevice[mkNetworkDeviceEnabled] = nd.Enabled
if nd.Firewall != nil {
networkDevice[mkNetworkDeviceFirewall] = *nd.Firewall
} else {
networkDevice[mkNetworkDeviceFirewall] = false
}
if nd.MACAddress != nil {
macAddresses[ni] = *nd.MACAddress
} else {
macAddresses[ni] = ""
}
networkDevice[mkNetworkDeviceMACAddress] = macAddresses[ni]
networkDevice[mkNetworkDeviceModel] = nd.Model
if nd.Queues != nil {
networkDevice[mkNetworkDeviceQueues] = *nd.Queues
} else {
networkDevice[mkNetworkDeviceQueues] = 0
}
if nd.RateLimit != nil {
networkDevice[mkNetworkDeviceRateLimit] = *nd.RateLimit
} else {
networkDevice[mkNetworkDeviceRateLimit] = 0
}
if nd.Tag != nil {
networkDevice[mkNetworkDeviceVLANID] = nd.Tag
} else {
networkDevice[mkNetworkDeviceVLANID] = 0
}
if nd.Trunks != nil {
networkDevice[mkNetworkDeviceTrunks] = strings.Trim(
strings.Join(strings.Fields(fmt.Sprint(nd.Trunks)), ";"), "[]")
} else {
networkDevice[mkNetworkDeviceTrunks] = ""
}
if nd.MTU != nil {
networkDevice[mkNetworkDeviceMTU] = nd.MTU
} else {
networkDevice[mkNetworkDeviceMTU] = 0
}
} else {
macAddresses[ni] = ""
networkDevice[mkNetworkDeviceEnabled] = false
}
networkDeviceList[ni] = networkDevice
}
err = d.Set(mkMACAddresses, macAddresses[0:len(currentNetworkDeviceList)])
diags = append(diags, diag.FromErr(err)...)
if len(currentNetworkDeviceList) > 0 || networkDeviceLast > -1 {
err := d.Set(mkNetworkDevice, networkDeviceList[:networkDeviceLast+1])
diags = append(diags, diag.FromErr(err)...)
}
// Compare the operating system configuration to the one stored in the state. // Compare the operating system configuration to the one stored in the state.
operatingSystem := map[string]interface{}{} operatingSystem := map[string]interface{}{}
@ -4455,112 +4123,23 @@ func vmReadCustom(
} }
} }
diags = append( vmAPI := api.Node(nodeName).VM(vmID)
diags, started := d.Get(mkStarted).(bool)
vmReadNetworkValues(ctx, d, m, vmID, vmConfig)...)
// during import these core attributes might not be set, so set them explicitly here agentTimeout, e := getAgentTimeout(d)
d.SetId(strconv.Itoa(vmID))
e := d.Set(mkVMID, vmID)
diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkNodeName, nodeName)
diags = append(diags, diag.FromErr(e)...)
return diags
}
func vmReadNetworkValues(
ctx context.Context,
d *schema.ResourceData,
m interface{},
vmID int,
vmConfig *vms.GetResponseData,
) diag.Diagnostics {
var diags diag.Diagnostics
config := m.(proxmoxtf.ProviderConfiguration)
api, e := config.GetClient()
if e != nil { if e != nil {
return diag.FromErr(e) return diag.FromErr(e)
} }
nodeName := d.Get(mkNodeName).(string) diags = append(
diags,
network.ReadNetworkValues(ctx, d, vmAPI, started, vmConfig, agentTimeout)...)
vmAPI := api.Node(nodeName).VM(vmID) // during import these core attributes might not be set, so set them explicitly here
d.SetId(strconv.Itoa(vmID))
started := d.Get(mkStarted).(bool) e = d.Set(mkVMID, vmID)
var ipv4Addresses []interface{}
var ipv6Addresses []interface{}
var networkInterfaceNames []interface{}
if started {
if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled {
resource := VM()
agentBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkAgent},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
agentTimeout, err := time.ParseDuration(
agentBlock[mkAgentTimeout].(string),
)
if err != nil {
return diag.FromErr(err)
}
var macAddresses []interface{}
networkInterfaces, err := vmAPI.WaitForNetworkInterfacesFromVMAgent(ctx, int(agentTimeout.Seconds()), 5, true)
if err == nil && networkInterfaces.Result != nil {
ipv4Addresses = make([]interface{}, len(*networkInterfaces.Result))
ipv6Addresses = make([]interface{}, len(*networkInterfaces.Result))
macAddresses = make([]interface{}, len(*networkInterfaces.Result))
networkInterfaceNames = make([]interface{}, len(*networkInterfaces.Result))
for ri, rv := range *networkInterfaces.Result {
var rvIPv4Addresses []interface{}
var rvIPv6Addresses []interface{}
if rv.IPAddresses != nil {
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
macAddresses[ri] = strings.ToUpper(rv.MACAddress)
networkInterfaceNames[ri] = rv.Name
}
}
err = d.Set(mkMACAddresses, macAddresses)
diags = append(diags, diag.FromErr(err)...)
}
}
e = d.Set(mkIPv4Addresses, ipv4Addresses)
diags = append(diags, diag.FromErr(e)...) diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkIPv6Addresses, ipv6Addresses) e = d.Set(mkNodeName, nodeName)
diags = append(diags, diag.FromErr(e)...)
e = d.Set(mkNetworkInterfaceNames, networkInterfaceNames)
diags = append(diags, diag.FromErr(e)...) diags = append(diags, diag.FromErr(e)...)
return diags return diags
@ -5257,8 +4836,9 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
} }
// Prepare the new network device configuration. // Prepare the new network device configuration.
if d.HasChange(mkNetworkDevice) {
updateBody.NetworkDevices, err = vmGetNetworkDeviceObjects(d) if d.HasChange(network.MkNetworkDevice) {
updateBody.NetworkDevices, err = network.GetNetworkDeviceObjects(d)
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
@ -5269,7 +4849,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
} }
} }
for i := len(updateBody.NetworkDevices); i < maxResourceVirtualEnvironmentVMNetworkDevices; i++ { for i := len(updateBody.NetworkDevices); i < network.MaxNetworkDevices; i++ {
del = append(del, fmt.Sprintf("net%d", i)) del = append(del, fmt.Sprintf("net%d", i))
} }
@ -5731,3 +5311,27 @@ func parseImportIDWithNodeName(id string) (string, string, error) {
return nodeName, id, nil return nodeName, id, nil
} }
func getAgentTimeout(d *schema.ResourceData) (time.Duration, error) {
resource := VM()
agentBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkAgent},
0,
true,
)
if err != nil {
return 0, fmt.Errorf("failed to get agent block: %w", err)
}
agentTimeout, err := time.ParseDuration(
agentBlock[mkAgentTimeout].(string),
)
if err != nil {
return 0, fmt.Errorf("failed to parse agent timeout: %w", err)
}
return agentTimeout, nil
}

View File

@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/network"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
) )
@ -20,8 +21,8 @@ import (
func TestVMInstantiation(t *testing.T) { func TestVMInstantiation(t *testing.T) {
t.Parallel() t.Parallel()
s := VM() r := VM()
if s == nil { if r == nil {
t.Fatalf("Cannot instantiate VM") t.Fatalf("Cannot instantiate VM")
} }
} }
@ -30,7 +31,7 @@ func TestVMInstantiation(t *testing.T) {
func TestVMSchema(t *testing.T) { func TestVMSchema(t *testing.T) {
t.Parallel() t.Parallel()
s := VM() s := VM().Schema
test.AssertRequiredArguments(t, s, []string{ test.AssertRequiredArguments(t, s, []string{
mkNodeName, mkNodeName,
@ -56,7 +57,7 @@ func TestVMSchema(t *testing.T) {
mkMachine, mkMachine,
mkMemory, mkMemory,
mkName, mkName,
mkNetworkDevice, network.MkNetworkDevice,
mkOperatingSystem, mkOperatingSystem,
mkPoolID, mkPoolID,
mkSerialDevice, mkSerialDevice,
@ -67,45 +68,33 @@ func TestVMSchema(t *testing.T) {
mkSCSIHardware, mkSCSIHardware,
}) })
test.AssertComputedAttributes(t, s, []string{
mkIPv4Addresses,
mkIPv6Addresses,
mkMACAddresses,
mkNetworkInterfaceNames,
})
test.AssertValueTypes(t, s, map[string]schema.ValueType{ test.AssertValueTypes(t, s, map[string]schema.ValueType{
mkACPI: schema.TypeBool, mkACPI: schema.TypeBool,
mkAgent: schema.TypeList, mkAgent: schema.TypeList,
mkAudioDevice: schema.TypeList, mkAudioDevice: schema.TypeList,
mkBIOS: schema.TypeString, mkBIOS: schema.TypeString,
mkBootOrder: schema.TypeList, mkBootOrder: schema.TypeList,
mkCDROM: schema.TypeList, mkCDROM: schema.TypeList,
mkCPU: schema.TypeList, mkCPU: schema.TypeList,
mkDescription: schema.TypeString, mkDescription: schema.TypeString,
disk.MkDisk: schema.TypeList, disk.MkDisk: schema.TypeList,
mkEFIDisk: schema.TypeList, mkEFIDisk: schema.TypeList,
mkHostPCI: schema.TypeList, mkHostPCI: schema.TypeList,
mkHostUSB: schema.TypeList, mkHostUSB: schema.TypeList,
mkInitialization: schema.TypeList, mkInitialization: schema.TypeList,
mkIPv4Addresses: schema.TypeList, mkKeyboardLayout: schema.TypeString,
mkIPv6Addresses: schema.TypeList, mkKVMArguments: schema.TypeString,
mkKeyboardLayout: schema.TypeString, mkMachine: schema.TypeString,
mkKVMArguments: schema.TypeString, mkMemory: schema.TypeList,
mkMachine: schema.TypeString, mkName: schema.TypeString,
mkMemory: schema.TypeList, mkOperatingSystem: schema.TypeList,
mkName: schema.TypeString, mkPoolID: schema.TypeString,
mkNetworkDevice: schema.TypeList, mkSerialDevice: schema.TypeList,
mkMACAddresses: schema.TypeList, mkStarted: schema.TypeBool,
mkNetworkInterfaceNames: schema.TypeList, mkTabletDevice: schema.TypeBool,
mkOperatingSystem: schema.TypeList, mkTemplate: schema.TypeBool,
mkPoolID: schema.TypeString, mkVMID: schema.TypeInt,
mkSerialDevice: schema.TypeList, mkSCSIHardware: schema.TypeString,
mkStarted: schema.TypeBool,
mkTabletDevice: schema.TypeBool,
mkTemplate: schema.TypeBool,
mkVMID: schema.TypeInt,
mkSCSIHardware: schema.TypeString,
}) })
agentSchema := test.AssertNestedSchemaExistence(t, s, mkAgent) agentSchema := test.AssertNestedSchemaExistence(t, s, mkAgent)
@ -188,44 +177,6 @@ func TestVMSchema(t *testing.T) {
mkCPUUnits: schema.TypeInt, mkCPUUnits: schema.TypeInt,
}) })
// diskSchema := test.AssertNestedSchemaExistence(t, s, mkDisk)
//
// test.AssertOptionalArguments(t, diskSchema, []string{
// mkDiskDatastoreID,
// mkDiskPathInDatastore,
// mkDiskFileFormat,
// mkDiskFileID,
// mkDiskSize,
// })
//
// test.AssertValueTypes(t, diskSchema, map[string]schema.ValueType{
// mkDiskDatastoreID: schema.TypeString,
// mkDiskPathInDatastore: schema.TypeString,
// mkDiskFileFormat: schema.TypeString,
// mkDiskFileID: schema.TypeString,
// mkDiskSize: schema.TypeInt,
// })
//
// diskSpeedSchema := test.AssertNestedSchemaExistence(
// t,
// diskSchema,
// mkDiskSpeed,
//)
//
// test.AssertOptionalArguments(t, diskSpeedSchema, []string{
// mkDiskSpeedRead,
// mkDiskSpeedReadBurstable,
// mkDiskSpeedWrite,
// mkDiskSpeedWriteBurstable,
// })
//
// test.AssertValueTypes(t, diskSpeedSchema, map[string]schema.ValueType{
// mkDiskSpeedRead: schema.TypeInt,
// mkDiskSpeedReadBurstable: schema.TypeInt,
// mkDiskSpeedWrite: schema.TypeInt,
// mkDiskSpeedWriteBurstable: schema.TypeInt,
// })
efiDiskSchema := test.AssertNestedSchemaExistence(t, s, mkEFIDisk) efiDiskSchema := test.AssertNestedSchemaExistence(t, s, mkEFIDisk)
test.AssertOptionalArguments(t, efiDiskSchema, []string{ test.AssertOptionalArguments(t, efiDiskSchema, []string{
@ -390,32 +341,6 @@ func TestVMSchema(t *testing.T) {
mkMemoryShared: schema.TypeInt, mkMemoryShared: schema.TypeInt,
}) })
networkDeviceSchema := test.AssertNestedSchemaExistence(
t,
s,
mkNetworkDevice,
)
test.AssertOptionalArguments(t, networkDeviceSchema, []string{
mkNetworkDeviceBridge,
mkNetworkDeviceEnabled,
mkNetworkDeviceMACAddress,
mkNetworkDeviceModel,
mkNetworkDeviceRateLimit,
mkNetworkDeviceVLANID,
mkNetworkDeviceMTU,
})
test.AssertValueTypes(t, networkDeviceSchema, map[string]schema.ValueType{
mkNetworkDeviceBridge: schema.TypeString,
mkNetworkDeviceEnabled: schema.TypeBool,
mkNetworkDeviceMACAddress: schema.TypeString,
mkNetworkDeviceModel: schema.TypeString,
mkNetworkDeviceRateLimit: schema.TypeFloat,
mkNetworkDeviceVLANID: schema.TypeInt,
mkNetworkDeviceMTU: schema.TypeInt,
})
operatingSystemSchema := test.AssertNestedSchemaExistence( operatingSystemSchema := test.AssertNestedSchemaExistence(
t, t,
s, s,

View File

@ -15,20 +15,20 @@ import (
) )
// AssertComputedAttributes checks that the given schema has the given computed attributes. // AssertComputedAttributes checks that the given schema has the given computed attributes.
func AssertComputedAttributes(t *testing.T, s *schema.Resource, keys []string) { func AssertComputedAttributes(t *testing.T, s map[string]*schema.Schema, keys []string) {
t.Helper() t.Helper()
for _, v := range keys { for _, v := range keys {
require.NotNil(t, s.Schema[v], "Error in Schema: Missing definition for \"%s\"", v) require.NotNil(t, s[v], "Error in Schema: Missing definition for \"%s\"", v)
assert.True(t, s.Schema[v].Computed, "Error in Schema: Attribute \"%s\" is not computed", v) assert.True(t, s[v].Computed, "Error in Schema: Attribute \"%s\" is not computed", v)
} }
} }
// AssertNestedSchemaExistence checks that the given schema has a nested schema for the given key. // AssertNestedSchemaExistence checks that the given schema has a nested schema for the given key.
func AssertNestedSchemaExistence(t *testing.T, s *schema.Resource, key string) *schema.Resource { func AssertNestedSchemaExistence(t *testing.T, s map[string]*schema.Schema, key string) map[string]*schema.Schema {
t.Helper() t.Helper()
sh, ok := s.Schema[key].Elem.(*schema.Resource) r, ok := s[key].Elem.(*schema.Resource)
if !ok { if !ok {
t.Fatalf("Error in Schema: Missing nested schema for \"%s\"", key) t.Fatalf("Error in Schema: Missing nested schema for \"%s\"", key)
@ -36,45 +36,45 @@ func AssertNestedSchemaExistence(t *testing.T, s *schema.Resource, key string) *
return nil return nil
} }
return sh return r.Schema
} }
// AssertListMaxItems checks that the given schema attribute has given expected MaxItems value. // AssertListMaxItems checks that the given schema attribute has given expected MaxItems value.
func AssertListMaxItems(t *testing.T, s *schema.Resource, key string, expectedMaxItems int) { func AssertListMaxItems(t *testing.T, s map[string]*schema.Schema, key string, expectedMaxItems int) {
t.Helper() t.Helper()
require.NotNil(t, s.Schema[key], "Error in Schema: Missing definition for \"%s\"", key) require.NotNil(t, s[key], "Error in Schema: Missing definition for \"%s\"", key)
assert.Equal(t, expectedMaxItems, s.Schema[key].MaxItems, assert.Equal(t, expectedMaxItems, s[key].MaxItems,
"Error in Schema: Argument \"%s\" has \"MaxItems: %#v\", but value %#v is expected!", "Error in Schema: Argument \"%s\" has \"MaxItems: %#v\", but value %#v is expected!",
key, s.Schema[key].MaxItems, expectedMaxItems) key, s[key].MaxItems, expectedMaxItems)
} }
// AssertOptionalArguments checks that the given schema has the given optional arguments. // AssertOptionalArguments checks that the given schema has the given optional arguments.
func AssertOptionalArguments(t *testing.T, s *schema.Resource, keys []string) { func AssertOptionalArguments(t *testing.T, s map[string]*schema.Schema, keys []string) {
t.Helper() t.Helper()
for _, v := range keys { for _, v := range keys {
require.NotNil(t, s.Schema[v], "Error in Schema: Missing definition for \"%s\"", v) require.NotNil(t, s[v], "Error in Schema: Missing definition for \"%s\"", v)
assert.True(t, s.Schema[v].Optional, "Error in Schema: Argument \"%s\" is not optional", v) assert.True(t, s[v].Optional, "Error in Schema: Argument \"%s\" is not optional", v)
} }
} }
// AssertRequiredArguments checks that the given schema has the given required arguments. // AssertRequiredArguments checks that the given schema has the given required arguments.
func AssertRequiredArguments(t *testing.T, s *schema.Resource, keys []string) { func AssertRequiredArguments(t *testing.T, s map[string]*schema.Schema, keys []string) {
t.Helper() t.Helper()
for _, v := range keys { for _, v := range keys {
require.NotNil(t, s.Schema[v], "Error in Schema: Missing definition for \"%s\"", v) require.NotNil(t, s[v], "Error in Schema: Missing definition for \"%s\"", v)
assert.True(t, s.Schema[v].Required, "Error in Schema: Argument \"%s\" is not required", v) assert.True(t, s[v].Required, "Error in Schema: Argument \"%s\" is not required", v)
} }
} }
// AssertValueTypes checks that the given schema has the given value types for the given fields. // AssertValueTypes checks that the given schema has the given value types for the given fields.
func AssertValueTypes(t *testing.T, s *schema.Resource, f map[string]schema.ValueType) { func AssertValueTypes(t *testing.T, s map[string]*schema.Schema, f map[string]schema.ValueType) {
t.Helper() t.Helper()
for fn, ft := range f { for fn, ft := range f {
require.NotNil(t, s.Schema[fn], "Error in Schema: Missing definition for \"%s\"", fn) require.NotNil(t, s[fn], "Error in Schema: Missing definition for \"%s\"", fn)
assert.Equal(t, ft, s.Schema[fn].Type, "Error in Schema: Argument or attribute \"%s\" is not of type \"%v\"", fn, ft) assert.Equal(t, ft, s[fn].Type, "Error in Schema: Argument or attribute \"%s\" is not of type \"%v\"", fn, ft)
} }
} }