From b2a27f3ccfa6e318d2243bae2c855f47e5523240 Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Thu, 20 Jul 2023 19:58:19 -0400 Subject: [PATCH] chore: cleanup resource validators & utility code (#438) * chore: cleanup resource validators & utility code * fix linter errors --- Makefile | 2 +- proxmoxtf/datasource/firewall/alias_test.go | 10 +- proxmoxtf/datasource/firewall/aliases_test.go | 8 +- proxmoxtf/datasource/firewall/ipset_test.go | 16 +- proxmoxtf/datasource/firewall/ipsets_test.go | 8 +- proxmoxtf/datasource/firewall/rule_test.go | 10 +- .../firewall/security_group_test.go | 10 +- .../firewall/security_groups_test.go | 8 +- .../cluster/firewall/security_group_test.go | 12 +- proxmoxtf/resource/container.go | 27 +- proxmoxtf/resource/file.go | 5 +- proxmoxtf/resource/firewall/alias_test.go | 10 +- proxmoxtf/resource/firewall/ipset_test.go | 18 +- proxmoxtf/resource/firewall/options_test.go | 8 +- proxmoxtf/resource/firewall/rules_test.go | 14 +- proxmoxtf/resource/utils.go | 582 ------------------ proxmoxtf/resource/validator/file.go | 78 +++ proxmoxtf/resource/validator/network.go | 52 +- proxmoxtf/resource/validator/vm.go | 221 +++++++ .../{utils_test.go => validator/vm_test.go} | 39 +- proxmoxtf/resource/vm.go | 205 +++++- proxmoxtf/resource/vm_test.go | 36 ++ proxmoxtf/structure/schema.go | 122 ++++ proxmoxtf/structure/test.go | 70 --- proxmoxtf/structure/tools.go | 25 - proxmoxtf/test/{test_utils.go => utils.go} | 0 26 files changed, 723 insertions(+), 873 deletions(-) delete mode 100644 proxmoxtf/resource/utils.go create mode 100644 proxmoxtf/resource/validator/file.go rename proxmoxtf/resource/{utils_test.go => validator/vm_test.go} (53%) create mode 100644 proxmoxtf/structure/schema.go delete mode 100644 proxmoxtf/structure/test.go delete mode 100644 proxmoxtf/structure/tools.go rename proxmoxtf/test/{test_utils.go => utils.go} (100%) diff --git a/Makefile b/Makefile index 5b9be4f9..d8a17b07 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ init: go get ./... test: - go test -v ./... + go test ./... lint: go run -modfile=tools/go.mod github.com/golangci/golangci-lint/cmd/golangci-lint run --fix diff --git a/proxmoxtf/datasource/firewall/alias_test.go b/proxmoxtf/datasource/firewall/alias_test.go index 7f2e8a3f..3c3079d2 100644 --- a/proxmoxtf/datasource/firewall/alias_test.go +++ b/proxmoxtf/datasource/firewall/alias_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestAliasSchemaInstantiation tests whether the AliasSchema instance can be instantiated. @@ -25,18 +25,18 @@ func TestAliasSchemaInstantiation(t *testing.T) { func TestAliasSchema(t *testing.T) { t.Parallel() - s := AliasSchema() + r := schema.Resource{Schema: AliasSchema()} - structure.AssertRequiredArguments(t, s, []string{ + test.AssertRequiredArguments(t, &r, []string{ mkAliasName, }) - structure.AssertComputedAttributes(t, s, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkAliasCIDR, mkAliasComment, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkAliasName: schema.TypeString, mkAliasCIDR: schema.TypeString, mkAliasComment: schema.TypeString, diff --git a/proxmoxtf/datasource/firewall/aliases_test.go b/proxmoxtf/datasource/firewall/aliases_test.go index 17640d18..81c7f9d6 100644 --- a/proxmoxtf/datasource/firewall/aliases_test.go +++ b/proxmoxtf/datasource/firewall/aliases_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestAliasesSchemaInstantiation tests whether the AliasesSchema instance can be instantiated. @@ -25,13 +25,13 @@ func TestAliasesSchemaInstantiation(t *testing.T) { func TestAliasesSchema(t *testing.T) { t.Parallel() - s := AliasesSchema() + r := schema.Resource{Schema: AliasesSchema()} - structure.AssertComputedAttributes(t, s, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkAliasesAliasNames, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkAliasesAliasNames: schema.TypeList, }) } diff --git a/proxmoxtf/datasource/firewall/ipset_test.go b/proxmoxtf/datasource/firewall/ipset_test.go index 1dcf25b9..778b5a1e 100644 --- a/proxmoxtf/datasource/firewall/ipset_test.go +++ b/proxmoxtf/datasource/firewall/ipset_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestIPSetSchemaInstantiation tests whether the IPSetSchema instance can be instantiated. @@ -25,32 +25,32 @@ func TestIPSetSchemaInstantiation(t *testing.T) { func TestIPSetSchema(t *testing.T) { t.Parallel() - s := IPSetSchema() + r := schema.Resource{Schema: IPSetSchema()} - structure.AssertRequiredArguments(t, s, []string{ + test.AssertRequiredArguments(t, &r, []string{ mkIPSetName, }) - structure.AssertComputedAttributes(t, s, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkIPSetCIDR, mkIPSetCIDRComment, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkIPSetName: schema.TypeString, mkIPSetCIDR: schema.TypeList, mkIPSetCIDRComment: schema.TypeString, }) - cirdSchema := structure.AssertNestedSchemaExistence(t, s, mkIPSetCIDR).Schema + cird := test.AssertNestedSchemaExistence(t, &r, mkIPSetCIDR) - structure.AssertComputedAttributes(t, cirdSchema, []string{ + test.AssertComputedAttributes(t, cird, []string{ mkIPSetCIDRName, mkIPSetCIDRNoMatch, mkIPSetCIDRComment, }) - structure.AssertValueTypes(t, cirdSchema, map[string]schema.ValueType{ + test.AssertValueTypes(t, cird, map[string]schema.ValueType{ mkIPSetCIDRName: schema.TypeString, mkIPSetCIDRNoMatch: schema.TypeBool, mkIPSetCIDRComment: schema.TypeString, diff --git a/proxmoxtf/datasource/firewall/ipsets_test.go b/proxmoxtf/datasource/firewall/ipsets_test.go index 03446766..d48f0cb4 100644 --- a/proxmoxtf/datasource/firewall/ipsets_test.go +++ b/proxmoxtf/datasource/firewall/ipsets_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestIPSetsSchemaInstantiation tests whether the IPSetsSchema instance can be instantiated. @@ -25,13 +25,13 @@ func TestIPSetsSchemaInstantiation(t *testing.T) { func TestIPSetsSchema(t *testing.T) { t.Parallel() - s := IPSetsSchema() + r := schema.Resource{Schema: IPSetsSchema()} - structure.AssertComputedAttributes(t, s, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkIPSetsIPSetNames, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkIPSetsIPSetNames: schema.TypeList, }) } diff --git a/proxmoxtf/datasource/firewall/rule_test.go b/proxmoxtf/datasource/firewall/rule_test.go index c137d5f7..58d44fdc 100644 --- a/proxmoxtf/datasource/firewall/rule_test.go +++ b/proxmoxtf/datasource/firewall/rule_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestRuleInstantiation tests whether the RuleSchema instance can be instantiated. @@ -25,14 +25,14 @@ func TestRuleSchemaInstantiation(t *testing.T) { func TestRuleSchema(t *testing.T) { t.Parallel() - ruleSchema := RuleSchema() + r := schema.Resource{Schema: RuleSchema()} - structure.AssertRequiredArguments(t, ruleSchema, []string{ + test.AssertRequiredArguments(t, &r, []string{ mkRuleAction, mkRuleType, }) - structure.AssertComputedAttributes(t, ruleSchema, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkRuleComment, mkRuleDest, mkRuleDPort, @@ -45,7 +45,7 @@ func TestRuleSchema(t *testing.T) { mkRuleSPort, }) - structure.AssertValueTypes(t, ruleSchema, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkRulePos: schema.TypeInt, mkRuleAction: schema.TypeString, mkRuleType: schema.TypeString, diff --git a/proxmoxtf/datasource/firewall/security_group_test.go b/proxmoxtf/datasource/firewall/security_group_test.go index 1066c0a0..d9c29011 100644 --- a/proxmoxtf/datasource/firewall/security_group_test.go +++ b/proxmoxtf/datasource/firewall/security_group_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestSecurityGroupSchemaInstantiation tests whether the SecurityGroupSchema instance can be instantiated. @@ -25,18 +25,18 @@ func TestSecurityGroupSchemaInstantiation(t *testing.T) { func TestSecurityGroupSchema(t *testing.T) { t.Parallel() - s := SecurityGroupSchema() + r := schema.Resource{Schema: SecurityGroupSchema()} - structure.AssertRequiredArguments(t, s, []string{ + test.AssertRequiredArguments(t, &r, []string{ mkSecurityGroupName, }) - structure.AssertComputedAttributes(t, s, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkSecurityGroupComment, mkRules, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkSecurityGroupName: schema.TypeString, mkSecurityGroupComment: schema.TypeString, mkRules: schema.TypeList, diff --git a/proxmoxtf/datasource/firewall/security_groups_test.go b/proxmoxtf/datasource/firewall/security_groups_test.go index d3995905..9ed00972 100644 --- a/proxmoxtf/datasource/firewall/security_groups_test.go +++ b/proxmoxtf/datasource/firewall/security_groups_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestSecurityGroupsSchemaInstantiation tests whether the SecurityGroupsSchema instance can be instantiated. @@ -25,13 +25,13 @@ func TestSecurityGroupsSchemaInstantiation(t *testing.T) { func TestSecurityGroupsSchema(t *testing.T) { t.Parallel() - s := SecurityGroupsSchema() + r := schema.Resource{Schema: SecurityGroupsSchema()} - structure.AssertComputedAttributes(t, s, []string{ + test.AssertComputedAttributes(t, &r, []string{ mkSecurityGroupsSecurityGroupNames, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, &r, map[string]schema.ValueType{ mkSecurityGroupsSecurityGroupNames: schema.TypeList, }) } diff --git a/proxmoxtf/resource/cluster/firewall/security_group_test.go b/proxmoxtf/resource/cluster/firewall/security_group_test.go index 0c8e599c..53c82209 100644 --- a/proxmoxtf/resource/cluster/firewall/security_group_test.go +++ b/proxmoxtf/resource/cluster/firewall/security_group_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/firewall" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestSecurityGroupInstantiation tests whether the SecurityGroup instance can be instantiated. @@ -26,20 +26,20 @@ func TestSecurityGroupInstantiation(t *testing.T) { func TestSecurityGroupSchema(t *testing.T) { t.Parallel() - s := SecurityGroup().Schema + s := SecurityGroup() - structure.AssertRequiredArguments(t, s, []string{ + test.AssertRequiredArguments(t, s, []string{ mkSecurityGroupName, }) - structure.AssertOptionalArguments(t, s, []string{ + test.AssertOptionalArguments(t, s, []string{ mkSecurityGroupComment, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, s, map[string]schema.ValueType{ mkSecurityGroupName: schema.TypeString, mkSecurityGroupComment: schema.TypeString, }) - structure.AssertNestedSchemaExistence(t, s, firewall.MkRule) + test.AssertNestedSchemaExistence(t, s, firewall.MkRule) } diff --git a/proxmoxtf/resource/container.go b/proxmoxtf/resource/container.go index dd1e4ec7..47dc6eea 100644 --- a/proxmoxtf/resource/container.go +++ b/proxmoxtf/resource/container.go @@ -21,6 +21,7 @@ import ( "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers" "github.com/bpg/terraform-provider-proxmox/proxmoxtf" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validator" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" ) const ( @@ -571,7 +572,7 @@ func Container() *schema.Resource { Description: "Volume size (only used for volume mount points)", Optional: true, Default: dvResourceVirtualEnvironmentContainerMountPointSize, - ValidateDiagFunc: getFileSizeValidator(), + ValidateDiagFunc: validator.FileSize(), }, mkResourceVirtualEnvironmentContainerMountPointVolume: { Type: schema.TypeString, @@ -618,7 +619,7 @@ func Container() *schema.Resource { DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { return new == "" }, - ValidateDiagFunc: getMACAddressValidator(), + ValidateDiagFunc: validator.MACAddress(), }, mkResourceVirtualEnvironmentContainerNetworkInterfaceName: { Type: schema.TypeString, @@ -666,7 +667,7 @@ func Container() *schema.Resource { Description: "The ID of an OS template file", Required: true, ForceNew: true, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentContainerOperatingSystemType: { Type: schema.TypeString, @@ -704,7 +705,7 @@ func Container() *schema.Resource { Type: schema.TypeString, ValidateFunc: validation.StringIsNotEmpty, }, - DiffSuppressFunc: suppressIfListsAreEqualIgnoringOrder, + DiffSuppressFunc: structure.SuppressIfListsAreEqualIgnoringOrder, DiffSuppressOnRefresh: true, }, mkResourceVirtualEnvironmentContainerTemplate: { @@ -1123,7 +1124,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string) resource := Container() - consoleBlock, err := getSchemaBlock( + consoleBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerConsole}, @@ -1140,7 +1141,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf consoleMode := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleMode].(string) consoleTTYCount := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleTTYCount].(int) - cpuBlock, err := getSchemaBlock( + cpuBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerCPU}, @@ -1157,7 +1158,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf description := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string) - diskBlock, err := getSchemaBlock( + diskBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerDisk}, @@ -1181,7 +1182,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf } } - featuresBlock, err := getSchemaBlock( + featuresBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerFeatures}, @@ -1282,7 +1283,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf } } - memoryBlock, err := getSchemaBlock( + memoryBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerMemory}, @@ -2273,7 +2274,7 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) // Prepare the new console configuration. if d.HasChange(mkResourceVirtualEnvironmentContainerConsole) { - consoleBlock, err := getSchemaBlock( + consoleBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerConsole}, @@ -2299,7 +2300,7 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) // Prepare the new CPU configuration. if d.HasChange(mkResourceVirtualEnvironmentContainerCPU) { - cpuBlock, err := getSchemaBlock( + cpuBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerCPU}, @@ -2396,7 +2397,7 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) // Prepare the new memory configuration. if d.HasChange(mkResourceVirtualEnvironmentContainerMemory) { - memoryBlock, err := getSchemaBlock( + memoryBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerMemory}, @@ -2561,7 +2562,7 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) // Prepare the new operating system configuration. if d.HasChange(mkResourceVirtualEnvironmentContainerOperatingSystem) { - operatingSystem, err := getSchemaBlock( + operatingSystem, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentContainerOperatingSystem}, diff --git a/proxmoxtf/resource/file.go b/proxmoxtf/resource/file.go index c63eaf67..7ab916ce 100644 --- a/proxmoxtf/resource/file.go +++ b/proxmoxtf/resource/file.go @@ -29,6 +29,7 @@ import ( "github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmoxtf" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validator" "github.com/bpg/terraform-provider-proxmox/utils" ) @@ -69,7 +70,7 @@ func File() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentFileContentType, - ValidateDiagFunc: getContentTypeValidator(), + ValidateDiagFunc: validator.ContentType(), }, mkResourceVirtualEnvironmentFileDatastoreID: { Type: schema.TypeString, @@ -523,7 +524,7 @@ func fileGetContentType(d *schema.ResourceData) (*string, diag.Diagnostics) { } } - ctValidator := getContentTypeValidator() + ctValidator := validator.ContentType() diags := ctValidator(contentType, cty.GetAttrPath(mkResourceVirtualEnvironmentFileContentType)) return &contentType, diags diff --git a/proxmoxtf/resource/firewall/alias_test.go b/proxmoxtf/resource/firewall/alias_test.go index 0f3392d7..6f34c471 100644 --- a/proxmoxtf/resource/firewall/alias_test.go +++ b/proxmoxtf/resource/firewall/alias_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestAliasInstantiation tests whether the Alias instance can be instantiated. @@ -25,20 +25,20 @@ func TestAliasInstantiation(t *testing.T) { func TestAliasSchema(t *testing.T) { t.Parallel() - s := Alias().Schema + s := Alias() - structure.AssertRequiredArguments(t, s, []string{ + test.AssertRequiredArguments(t, s, []string{ mkAliasName, mkAliasCIDR, }) - structure.AssertOptionalArguments(t, s, []string{ + test.AssertOptionalArguments(t, s, []string{ mkSelectorVMID, mkSelectorNodeName, mkAliasComment, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, s, map[string]schema.ValueType{ mkAliasName: schema.TypeString, mkAliasCIDR: schema.TypeString, mkAliasComment: schema.TypeString, diff --git a/proxmoxtf/resource/firewall/ipset_test.go b/proxmoxtf/resource/firewall/ipset_test.go index 8f2b0137..0b2a5e49 100644 --- a/proxmoxtf/resource/firewall/ipset_test.go +++ b/proxmoxtf/resource/firewall/ipset_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestIPSetInstantiation tests whether the IPSet @@ -26,37 +26,37 @@ func TestIPSetInstantiation(t *testing.T) { func TestIPSetSchema(t *testing.T) { t.Parallel() - s := IPSet().Schema + s := IPSet() - structure.AssertRequiredArguments(t, s, []string{ + test.AssertRequiredArguments(t, s, []string{ mkIPSetName, }) - structure.AssertOptionalArguments(t, s, []string{ + test.AssertOptionalArguments(t, s, []string{ mkSelectorVMID, mkSelectorNodeName, mkIPSetCIDR, mkIPSetCIDRComment, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, s, map[string]schema.ValueType{ mkIPSetName: schema.TypeString, mkIPSetCIDR: schema.TypeList, mkIPSetCIDRComment: schema.TypeString, }) - nested := structure.AssertNestedSchemaExistence(t, s, mkIPSetCIDR).Schema + nested := test.AssertNestedSchemaExistence(t, s, mkIPSetCIDR) - structure.AssertRequiredArguments(t, nested, []string{ + test.AssertRequiredArguments(t, nested, []string{ mkIPSetCIDRName, }) - structure.AssertOptionalArguments(t, nested, []string{ + test.AssertOptionalArguments(t, nested, []string{ mkIPSetCIDRComment, mkIPSetCIDRNoMatch, }) - structure.AssertValueTypes(t, nested, map[string]schema.ValueType{ + test.AssertValueTypes(t, nested, map[string]schema.ValueType{ mkIPSetCIDRName: schema.TypeString, mkIPSetCIDRComment: schema.TypeString, mkIPSetCIDRNoMatch: schema.TypeBool, diff --git a/proxmoxtf/resource/firewall/options_test.go b/proxmoxtf/resource/firewall/options_test.go index 179236e6..66a934f7 100644 --- a/proxmoxtf/resource/firewall/options_test.go +++ b/proxmoxtf/resource/firewall/options_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestOptionsInstantiation tests whether the Options instance can be instantiated. @@ -25,9 +25,9 @@ func TestOptionsInstantiation(t *testing.T) { func TestOptionsSchema(t *testing.T) { t.Parallel() - s := Options().Schema + s := Options() - structure.AssertOptionalArguments(t, s, []string{ + test.AssertOptionalArguments(t, s, []string{ mkDHCP, mkEnabled, mkIPFilter, @@ -40,7 +40,7 @@ func TestOptionsSchema(t *testing.T) { mkRadv, }) - structure.AssertValueTypes(t, s, map[string]schema.ValueType{ + test.AssertValueTypes(t, s, map[string]schema.ValueType{ mkDHCP: schema.TypeBool, mkEnabled: schema.TypeBool, mkIPFilter: schema.TypeBool, diff --git a/proxmoxtf/resource/firewall/rules_test.go b/proxmoxtf/resource/firewall/rules_test.go index a66a765d..41ec219a 100644 --- a/proxmoxtf/resource/firewall/rules_test.go +++ b/proxmoxtf/resource/firewall/rules_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) // TestRuleInstantiation tests whether the Rules instance can be instantiated. @@ -25,20 +25,20 @@ func TestRuleInstantiation(t *testing.T) { func TestRuleSchema(t *testing.T) { t.Parallel() - rulesSchema := Rules().Schema + rules := Rules() - structure.AssertRequiredArguments(t, rulesSchema, []string{ + test.AssertRequiredArguments(t, rules, []string{ MkRule, }) - structure.AssertOptionalArguments(t, rulesSchema, []string{ + test.AssertOptionalArguments(t, rules, []string{ mkSelectorVMID, mkSelectorNodeName, }) - ruleSchema := structure.AssertNestedSchemaExistence(t, rulesSchema, MkRule).Schema + nested := test.AssertNestedSchemaExistence(t, rules, MkRule) - structure.AssertOptionalArguments(t, ruleSchema, []string{ + test.AssertOptionalArguments(t, nested, []string{ mkSecurityGroup, mkRuleAction, mkRuleType, @@ -54,7 +54,7 @@ func TestRuleSchema(t *testing.T) { mkRuleSPort, }) - structure.AssertValueTypes(t, ruleSchema, map[string]schema.ValueType{ + test.AssertValueTypes(t, nested, map[string]schema.ValueType{ mkRulePos: schema.TypeInt, mkRuleAction: schema.TypeString, mkRuleType: schema.TypeString, diff --git a/proxmoxtf/resource/utils.go b/proxmoxtf/resource/utils.go deleted file mode 100644 index eb184cac..00000000 --- a/proxmoxtf/resource/utils.go +++ /dev/null @@ -1,582 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package resource - -import ( - "fmt" - "reflect" - "regexp" - "sort" - "strings" - "time" - "unicode" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - - "github.com/bpg/terraform-provider-proxmox/internal/types" - "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms" -) - -func getBIOSValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "ovmf", - "seabios", - }, false)) -} - -func getContentTypeValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "backup", - "iso", - "snippets", - "vztmpl", - }, false)) -} - -//nolint:unused -func getCPUFlagsValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(func(i interface{}, k string) (ws []string, es []error) { - list, ok := i.([]interface{}) - - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be []interface{}", k)) - return - } - - validator := validation.StringInSlice([]string{ - "+aes", - "-aes", - "+amd-no-ssb", - "-amd-no-ssb", - "+amd-ssbd", - "-amd-ssbd", - "+hv-evmcs", - "-hv-evmcs", - "+hv-tlbflush", - "-hv-tlbflush", - "+ibpb", - "-ibpb", - "+md-clear", - "-md-clear", - "+pcid", - "-pcid", - "+pdpe1gb", - "-pdpe1gb", - "+spec-ctrl", - "-spec-ctrl", - "+ssbd", - "-ssbd", - "+virt-ssbd", - "-virt-ssbd", - }, false) - - for li, lv := range list { - v, ok := lv.(string) - - if !ok { - es = append(es, fmt.Errorf("expected type of %s[%d] to be string", k, li)) - return - } - - warns, errs := validator(v, k) - - ws = append(ws, warns...) - es = append(es, errs...) - - if len(es) > 0 { - return - } - } - - return - }) -} - -func getCPUTypeValidator() schema.SchemaValidateDiagFunc { - standardTypes := []string{ - "486", - "Broadwell", - "Broadwell-IBRS", - "Broadwell-noTSX", - "Broadwell-noTSX-IBRS", - "Cascadelake-Server", - "Cascadelake-Server-noTSX", - "Cascadelake-Server-v2", - "Cascadelake-Server-v4", - "Cascadelake-Server-v5", - "Conroe", - "Cooperlake", - "Cooperlake-v2", - "EPYC", - "EPYC-IBPB", - "EPYC-Milan", - "EPYC-Rome", - "EPYC-Rome-v2", - "EPYC-v3", - "Haswell", - "Haswell-IBRS", - "Haswell-noTSX", - "Haswell-noTSX-IBRS", - "Icelake-Client", - "Icelake-Client-noTSX", - "Icelake-Server", - "Icelake-Server-noTSX", - "Icelake-Server-v3", - "Icelake-Server-v4", - "Icelake-Server-v5", - "Icelake-Server-v6", - "IvyBridge", - "IvyBridge-IBRS", - "KnightsMill", - "Nehalem", - "Nehalem-IBRS", - "Opteron_G1", - "Opteron_G2", - "Opteron_G3", - "Opteron_G4", - "Opteron_G5", - "Penryn", - "SandyBridge", - "SandyBridge-IBRS", - "SapphireRapids", - "Skylake-Client", - "Skylake-Client-IBRS", - "Skylake-Client-noTSX-IBRS", - "Skylake-Client-v4", - "Skylake-Server", - "Skylake-Server-IBRS", - "Skylake-Server-noTSX-IBRS", - "Skylake-Server-v4", - "Skylake-Server-v5", - "Westmere", - "Westmere-IBRS", - "athlon", - "core2duo", - "coreduo", - "host", - "kvm32", - "kvm64", - "max", - "pentium", - "pentium2", - "pentium3", - "phenom", - "qemu32", - "qemu64", - "x86-64-v2", - "x86-64-v2-AES", - "x86-64-v3", - "x86-64-v4", - } - - return validation.ToDiagFunc(validation.Any( - validation.StringInSlice(standardTypes, false), - validation.StringMatch(regexp.MustCompile(`^custom-.+$`), "must be a valid custom CPU type"), - )) -} - -func getFileFormatValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "qcow2", - "raw", - "vmdk", - }, false)) -} - -func getFileIDValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(func(i interface{}, k string) (ws []string, es []error) { - v, ok := i.(string) - - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return - } - - if v != "" { - r := regexp.MustCompile(`^(?i)[a-z\d\-_]+:([a-z\d\-_]+/)?.+$`) - ok := r.MatchString(v) - - if !ok { - es = append(es, fmt.Errorf("expected %s to be a valid file identifier (datastore-name:iso/some-file.img), got %s", k, v)) - return - } - } - - return - }) -} - -func getFileSizeValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) { - v, ok := i.(string) - var es []error - - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return nil, es - } - - if v != "" { - _, err := types.ParseDiskSize(v) - if err != nil { - es = append(es, fmt.Errorf("expected %s to be a valid file size (100, 1M, 1G), got %s", k, v)) - return nil, es - } - } - - return []string{}, es - }) -} - -func getKeyboardLayoutValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "da", - "de", - "de-ch", - "en-gb", - "en-us", - "es", - "fi", - "fr", - "fr-be", - "fr-ca", - "fr-ch", - "hu", - "is", - "it", - "ja", - "lt", - "mk", - "nl", - "no", - "pl", - "pt", - "pt-br", - "sl", - "sv", - "tr", - }, false)) -} - -func diskDigitPrefix(s string) string { - for i, r := range s { - if unicode.IsDigit(r) { - return s[:i] - } - } - return s -} - -func getMACAddressValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(func(i interface{}, k string) (ws []string, es []error) { - v, ok := i.(string) - - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return - } - - if v != "" { - r := regexp.MustCompile(`^[A-Z\d]{2}(:[A-Z\d]{2}){5}$`) - ok := r.MatchString(v) - - if !ok { - es = append(es, fmt.Errorf("expected %s to be a valid MAC address (A0:B1:C2:D3:E4:F5), got %s", k, v)) - return - } - } - - return - }) -} - -func getNetworkDeviceModelValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{"e1000", "rtl8139", "virtio", "vmxnet3"}, false)) -} - -func getQEMUAgentTypeValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{"isa", "virtio"}, false)) -} - -func getSchemaBlock(r *schema.Resource, d *schema.ResourceData, 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 getTimeoutValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(func(i interface{}, k string) (ws []string, es []error) { - v, ok := i.(string) - - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return - } - - _, err := time.ParseDuration(v) - - if err != nil { - es = append(es, fmt.Errorf("expected value of %s to be a duration - got: %s", k, v)) - return - } - - return - }) -} - -func getVGAMemoryValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.IntBetween(4, 512)) -} - -func getVGATypeValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "cirrus", - "qxl", - "qxl2", - "qxl3", - "qxl4", - "serial0", - "serial1", - "serial2", - "serial3", - "std", - "virtio", - "vmware", - }, false)) -} - -func getSCSIHardwareValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "lsi", - "lsi53c810", - "virtio-scsi-pci", - "virtio-scsi-single", - "megasas", - "pvscsi", - }, false)) -} - -func getIDEInterfaceValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "ide0", - "ide1", - "ide2", - "ide3", - }, false)) -} - -// suppressIfListsAreEqualIgnoringOrder is a customdiff.SuppressionFunc that suppresses -// changes to a list if the old and new lists are equal, ignoring the order of the -// elements. -// It will be called for each list item, so it is not super efficient. It is -// recommended to use it only for small lists. -// Ref: https://github.com/hashicorp/terraform-plugin-sdk/issues/477 -func suppressIfListsAreEqualIgnoringOrder(key, _, _ string, d *schema.ResourceData) bool { - // the key is a path to the list item, not the list itself, e.g. "tags.0" - lastDotIndex := strings.LastIndex(key, ".") - if lastDotIndex != -1 { - key = key[:lastDotIndex] - } - - oldData, newData := d.GetChange(key) - if oldData == nil || newData == nil { - return false - } - - oldArray := oldData.([]interface{}) - newArray := newData.([]interface{}) - - if len(oldArray) != len(newArray) { - return false - } - - oldEvents := make([]string, len(oldArray)) - newEvents := make([]string, len(newArray)) - - for i, oldEvt := range oldArray { - oldEvents[i] = fmt.Sprint(oldEvt) - } - - for j, newEvt := range newArray { - newEvents[j] = fmt.Sprint(newEvt) - } - - sort.Strings(oldEvents) - sort.Strings(newEvents) - - return reflect.DeepEqual(oldEvents, newEvents) -} - -func getDiskInfo(resp *vms.GetResponseData, d *schema.ResourceData) map[string]*vms.CustomStorageDevice { - currentDisk := d.Get(mkResourceVirtualEnvironmentVMDisk) - - currentDiskList := currentDisk.([]interface{}) - currentDiskMap := map[string]map[string]interface{}{} - - for _, v := range currentDiskList { - diskMap := v.(map[string]interface{}) - diskInterface := diskMap[mkResourceVirtualEnvironmentVMDiskInterface].(string) - - currentDiskMap[diskInterface] = diskMap - } - - storageDevices := map[string]*vms.CustomStorageDevice{} - - storageDevices["ide0"] = resp.IDEDevice0 - storageDevices["ide1"] = resp.IDEDevice1 - storageDevices["ide2"] = resp.IDEDevice2 - storageDevices["ide3"] = resp.IDEDevice3 - - storageDevices["sata0"] = resp.SATADevice0 - storageDevices["sata1"] = resp.SATADevice1 - storageDevices["sata2"] = resp.SATADevice2 - storageDevices["sata3"] = resp.SATADevice3 - storageDevices["sata4"] = resp.SATADevice4 - storageDevices["sata5"] = resp.SATADevice5 - - storageDevices["scsi0"] = resp.SCSIDevice0 - storageDevices["scsi1"] = resp.SCSIDevice1 - storageDevices["scsi2"] = resp.SCSIDevice2 - storageDevices["scsi3"] = resp.SCSIDevice3 - storageDevices["scsi4"] = resp.SCSIDevice4 - storageDevices["scsi5"] = resp.SCSIDevice5 - storageDevices["scsi6"] = resp.SCSIDevice6 - storageDevices["scsi7"] = resp.SCSIDevice7 - storageDevices["scsi8"] = resp.SCSIDevice8 - storageDevices["scsi9"] = resp.SCSIDevice9 - storageDevices["scsi10"] = resp.SCSIDevice10 - storageDevices["scsi11"] = resp.SCSIDevice11 - storageDevices["scsi12"] = resp.SCSIDevice12 - storageDevices["scsi13"] = resp.SCSIDevice13 - - storageDevices["virtio0"] = resp.VirtualIODevice0 - storageDevices["virtio1"] = resp.VirtualIODevice1 - storageDevices["virtio2"] = resp.VirtualIODevice2 - storageDevices["virtio3"] = resp.VirtualIODevice3 - storageDevices["virtio4"] = resp.VirtualIODevice4 - storageDevices["virtio5"] = resp.VirtualIODevice5 - storageDevices["virtio6"] = resp.VirtualIODevice6 - storageDevices["virtio7"] = resp.VirtualIODevice7 - storageDevices["virtio8"] = resp.VirtualIODevice8 - storageDevices["virtio9"] = resp.VirtualIODevice9 - storageDevices["virtio10"] = resp.VirtualIODevice10 - storageDevices["virtio11"] = resp.VirtualIODevice11 - storageDevices["virtio12"] = resp.VirtualIODevice12 - storageDevices["virtio13"] = resp.VirtualIODevice13 - storageDevices["virtio14"] = resp.VirtualIODevice14 - storageDevices["virtio15"] = resp.VirtualIODevice15 - - for k, v := range storageDevices { - if v != nil { - if currentDiskMap[k] != nil { - if currentDiskMap[k][mkResourceVirtualEnvironmentVMDiskFileID] != nil { - fileID := currentDiskMap[k][mkResourceVirtualEnvironmentVMDiskFileID].(string) - v.FileID = &fileID - } - } - // defensive copy of the loop variable - iface := k - v.Interface = &iface - } - } - - return storageDevices -} - -// getDiskDatastores returns a list of the used datastores in a VM -func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string { - storageDevices := getDiskInfo(vm, d) - datastoresSet := map[string]int{} - - for _, diskInfo := range storageDevices { - // Ignore empty storage devices and storage devices (like ide) which may not have any media mounted - if diskInfo == nil || diskInfo.FileVolume == "none" { - continue - } - fileIDParts := strings.Split(diskInfo.FileVolume, ":") - datastoresSet[fileIDParts[0]] = 1 - } - - if vm.EFIDisk != nil { - fileIDParts := strings.Split(vm.EFIDisk.FileVolume, ":") - datastoresSet[fileIDParts[0]] = 1 - } - - datastores := []string{} - for datastore := range datastoresSet { - datastores = append(datastores, datastore) - } - - return datastores -} - -func getPCIInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomPCIDevice { - pciDevices := map[string]*vms.CustomPCIDevice{} - - pciDevices["hostpci0"] = resp.PCIDevice0 - pciDevices["hostpci1"] = resp.PCIDevice1 - pciDevices["hostpci2"] = resp.PCIDevice2 - pciDevices["hostpci3"] = resp.PCIDevice3 - - return pciDevices -} - -func getCloudInitTypeValidator() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "configdrive2", - "nocloud", - }, false)) -} - -func parseImportIDWithNodeName(id string) (string, string, error) { - nodeName, id, found := strings.Cut(id, "/") - - if !found { - return "", "", fmt.Errorf("unexpected format of ID (%s), expected node/id", id) - } - - return nodeName, id, nil -} diff --git a/proxmoxtf/resource/validator/file.go b/proxmoxtf/resource/validator/file.go new file mode 100644 index 00000000..2c72d514 --- /dev/null +++ b/proxmoxtf/resource/validator/file.go @@ -0,0 +1,78 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package validator + +import ( + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/bpg/terraform-provider-proxmox/internal/types" +) + +// FileFormat returns a schema validation function for a file format. +func FileFormat() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "qcow2", + "raw", + "vmdk", + }, false)) +} + +// FileID returns a schema validation function for a file identifier. +func FileID() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + + var ws []string + var es []error + + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return ws, es + } + + if v != "" { + r := regexp.MustCompile(`^(?i)[a-z\d\-_]+:([a-z\d\-_]+/)?.+$`) + ok := r.MatchString(v) + + if !ok { + es = append(es, fmt.Errorf( + "expected %s to be a valid file identifier (datastore-name:iso/some-file.img), got %s", k, v, + )) + return ws, es + } + } + + return ws, es + }) +} + +// FileSize is a schema validation function for file size. +func FileSize() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + var es []error + + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return nil, es + } + + if v != "" { + _, err := types.ParseDiskSize(v) + if err != nil { + es = append(es, fmt.Errorf("expected %s to be a valid file size (100, 1M, 1G), got %s", k, v)) + return nil, es + } + } + + return []string{}, es + }) +} diff --git a/proxmoxtf/resource/validator/network.go b/proxmoxtf/resource/validator/network.go index 698a6b1a..dc78c181 100644 --- a/proxmoxtf/resource/validator/network.go +++ b/proxmoxtf/resource/validator/network.go @@ -8,69 +8,35 @@ package validator import ( "fmt" + "regexp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -// VLANIDsValidator returns a schema validation function for VLAN IDs. -func VLANIDsValidator() schema.SchemaValidateDiagFunc { +// MACAddress is a schema validation function for MAC address. +func MACAddress() schema.SchemaValidateDiagFunc { return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) { - min := 1 - max := 4094 + v, ok := i.(string) var ws []string var es []error - list, ok := i.([]interface{}) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be []interface{}", k)) + es = append(es, fmt.Errorf("expected type of %s to be string", k)) return ws, es } - for li, lv := range list { - v, ok := lv.(int) + if v != "" { + r := regexp.MustCompile(`^[A-Z\d]{2}(:[A-Z\d]{2}){5}$`) + ok := r.MatchString(v) if !ok { - es = append(es, fmt.Errorf("expected type of %s[%d] to be int", k, li)) + es = append(es, fmt.Errorf("expected %s to be a valid MAC address (A0:B1:C2:D3:E4:F5), got %s", k, v)) return ws, es } - - if v != -1 { - if v < min || v > max { - es = append(es, fmt.Errorf("expected %s[%d] to be in the range (%d - %d), got %d", k, li, min, max, v)) - return ws, es - } - } } return ws, es }) } - -// NodeNetworkInterfaceBondingModes returns a schema validation function for a node network interface bonding mode. -func NodeNetworkInterfaceBondingModes() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "balance-rr", - "active-backup", - "balance-xor", - "broadcast", - "802.3ad", - "balance-tlb", - "balance-alb", - "balance-slb", - "lacp-balance-slb", - "lacp-balance-tcp", - }, false)) -} - -// NodeNetworkInterfaceBondingTransmitHashPolicies returns a schema validation function for a node network interface -// bonding transmit hash policy. -func NodeNetworkInterfaceBondingTransmitHashPolicies() schema.SchemaValidateDiagFunc { - return validation.ToDiagFunc(validation.StringInSlice([]string{ - "layer2", - "layer2+3", - "layer3+4", - }, false)) -} diff --git a/proxmoxtf/resource/validator/vm.go b/proxmoxtf/resource/validator/vm.go index a7a62de8..0c6cce98 100644 --- a/proxmoxtf/resource/validator/vm.go +++ b/proxmoxtf/resource/validator/vm.go @@ -8,6 +8,8 @@ package validator import ( "fmt" + "regexp" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -39,3 +41,222 @@ func VMID() schema.SchemaValidateDiagFunc { return ws, es }) } + +// BIOS returns a schema validation function for a BIOS type. +func BIOS() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "ovmf", + "seabios", + }, false)) +} + +// ContentType returns a schema validation function for a content type on a storage device. +func ContentType() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "backup", + "iso", + "snippets", + "vztmpl", + }, false)) +} + +// CPUType returns a schema validation function for a CPU type. +func CPUType() schema.SchemaValidateDiagFunc { + standardTypes := []string{ + "486", + "Broadwell", + "Broadwell-IBRS", + "Broadwell-noTSX", + "Broadwell-noTSX-IBRS", + "Cascadelake-Server", + "Cascadelake-Server-noTSX", + "Cascadelake-Server-v2", + "Cascadelake-Server-v4", + "Cascadelake-Server-v5", + "Conroe", + "Cooperlake", + "Cooperlake-v2", + "EPYC", + "EPYC-IBPB", + "EPYC-Milan", + "EPYC-Rome", + "EPYC-Rome-v2", + "EPYC-v3", + "Haswell", + "Haswell-IBRS", + "Haswell-noTSX", + "Haswell-noTSX-IBRS", + "Icelake-Client", + "Icelake-Client-noTSX", + "Icelake-Server", + "Icelake-Server-noTSX", + "Icelake-Server-v3", + "Icelake-Server-v4", + "Icelake-Server-v5", + "Icelake-Server-v6", + "IvyBridge", + "IvyBridge-IBRS", + "KnightsMill", + "Nehalem", + "Nehalem-IBRS", + "Opteron_G1", + "Opteron_G2", + "Opteron_G3", + "Opteron_G4", + "Opteron_G5", + "Penryn", + "SandyBridge", + "SandyBridge-IBRS", + "SapphireRapids", + "Skylake-Client", + "Skylake-Client-IBRS", + "Skylake-Client-noTSX-IBRS", + "Skylake-Client-v4", + "Skylake-Server", + "Skylake-Server-IBRS", + "Skylake-Server-noTSX-IBRS", + "Skylake-Server-v4", + "Skylake-Server-v5", + "Westmere", + "Westmere-IBRS", + "athlon", + "core2duo", + "coreduo", + "host", + "kvm32", + "kvm64", + "max", + "pentium", + "pentium2", + "pentium3", + "phenom", + "qemu32", + "qemu64", + "x86-64-v2", + "x86-64-v2-AES", + "x86-64-v3", + "x86-64-v4", + } + + return validation.ToDiagFunc(validation.Any( + validation.StringInSlice(standardTypes, false), + validation.StringMatch(regexp.MustCompile(`^custom-.+$`), "must be a valid custom CPU type"), + )) +} + +// NetworkDeviceModel is a schema validation function for network device models. +func NetworkDeviceModel() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{"e1000", "rtl8139", "virtio", "vmxnet3"}, false)) +} + +// QEMUAgentType is a schema validation function for QEMU agent types. +func QEMUAgentType() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{"isa", "virtio"}, false)) +} + +// KeyboardLayout is a schema validation function for keyboard layouts. +func KeyboardLayout() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "da", + "de", + "de-ch", + "en-gb", + "en-us", + "es", + "fi", + "fr", + "fr-be", + "fr-ca", + "fr-ch", + "hu", + "is", + "it", + "ja", + "lt", + "mk", + "nl", + "no", + "pl", + "pt", + "pt-br", + "sl", + "sv", + "tr", + }, false)) +} + +// Timeout is a schema validation function for timeouts. +func Timeout() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + + var ws []string + var es []error + + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return ws, es + } + + _, err := time.ParseDuration(v) + if err != nil { + es = append(es, fmt.Errorf("expected value of %s to be a duration - got: %s", k, v)) + return ws, es + } + + return ws, es + }) +} + +// VGAMemory is a schema validation function for VGA memory sizes. +func VGAMemory() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.IntBetween(4, 512)) +} + +// VGAType is a schema validation function for VGA device types. +func VGAType() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "cirrus", + "qxl", + "qxl2", + "qxl3", + "qxl4", + "serial0", + "serial1", + "serial2", + "serial3", + "std", + "virtio", + "vmware", + }, false)) +} + +// SCSIHardware is a schema validation function for SCSI hardware. +func SCSIHardware() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "lsi", + "lsi53c810", + "virtio-scsi-pci", + "virtio-scsi-single", + "megasas", + "pvscsi", + }, false)) +} + +// IDEInterface is a schema validation function for IDE interfaces. +func IDEInterface() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "ide0", + "ide1", + "ide2", + "ide3", + }, false)) +} + +// CloudInitType is a schema validation function for cloud-init types. +func CloudInitType() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(validation.StringInSlice([]string{ + "configdrive2", + "nocloud", + }, false)) +} diff --git a/proxmoxtf/resource/utils_test.go b/proxmoxtf/resource/validator/vm_test.go similarity index 53% rename from proxmoxtf/resource/utils_test.go rename to proxmoxtf/resource/validator/vm_test.go index ae9e9bdf..e0b90064 100644 --- a/proxmoxtf/resource/utils_test.go +++ b/proxmoxtf/resource/validator/vm_test.go @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -package resource +package validator import ( "testing" @@ -33,7 +33,7 @@ func Test_getCPUTypeValidator(t *testing.T) { t.Parallel() require := require.New(t) - f := getCPUTypeValidator() + f := CPUType() res := f(tt.value, nil) if tt.valid { @@ -44,38 +44,3 @@ func Test_getCPUTypeValidator(t *testing.T) { }) } } - -func Test_parseImportIDWIthNodeName(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - value string - valid bool - expectedNodeName string - expectedID string - }{ - {"empty", "", false, "", ""}, - {"missing slash", "invalid", false, "", ""}, - {"valid", "host/id", true, "host", "id"}, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - require := require.New(t) - - nodeName, id, err := parseImportIDWithNodeName(tt.value) - - if !tt.valid { - require.Error(err) - return - } - - require.Nil(err) - require.Equal(tt.expectedNodeName, nodeName) - require.Equal(tt.expectedID, id) - }) - } -} diff --git a/proxmoxtf/resource/vm.go b/proxmoxtf/resource/vm.go index 150c338f..155aad49 100644 --- a/proxmoxtf/resource/vm.go +++ b/proxmoxtf/resource/vm.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "time" + "unicode" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" @@ -25,6 +26,7 @@ import ( "github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms" "github.com/bpg/terraform-provider-proxmox/proxmoxtf" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validator" + "github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure" ) const ( @@ -305,7 +307,7 @@ func VM() *schema.Resource { Description: "The maximum amount of time to wait for data from the QEMU agent to become available", Optional: true, Default: dvResourceVirtualEnvironmentVMAgentTimeout, - ValidateDiagFunc: getTimeoutValidator(), + ValidateDiagFunc: validator.Timeout(), }, mkResourceVirtualEnvironmentVMAgentTrim: { Type: schema.TypeBool, @@ -318,7 +320,7 @@ func VM() *schema.Resource { Description: "The QEMU agent interface type", Optional: true, Default: dvResourceVirtualEnvironmentVMAgentType, - ValidateDiagFunc: getQEMUAgentTypeValidator(), + ValidateDiagFunc: validator.QEMUAgentType(), }, }, }, @@ -370,7 +372,7 @@ func VM() *schema.Resource { Description: "The BIOS implementation", Optional: true, Default: dvResourceVirtualEnvironmentVMBIOS, - ValidateDiagFunc: getBIOSValidator(), + ValidateDiagFunc: validator.BIOS(), }, mkResourceVirtualEnvironmentVMCDROM: { Type: schema.TypeList, @@ -398,14 +400,14 @@ func VM() *schema.Resource { Description: "The file id", Optional: true, Default: dvResourceVirtualEnvironmentVMCDROMFileID, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentVMCDROMInterface: { Type: schema.TypeString, Description: "The CDROM interface", Optional: true, Default: dvResourceVirtualEnvironmentVMCDROMInterface, - ValidateDiagFunc: getIDEInterfaceValidator(), + ValidateDiagFunc: validator.IDEInterface(), }, }, }, @@ -529,7 +531,7 @@ func VM() *schema.Resource { Description: "The emulated CPU type", Optional: true, Default: dvResourceVirtualEnvironmentVMCPUType, - ValidateDiagFunc: getCPUTypeValidator(), + ValidateDiagFunc: validator.CPUType(), }, mkResourceVirtualEnvironmentVMCPUUnits: { Type: schema.TypeInt, @@ -588,7 +590,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Computed: true, - ValidateDiagFunc: getFileFormatValidator(), + ValidateDiagFunc: validator.FileFormat(), }, mkResourceVirtualEnvironmentVMDiskFileID: { Type: schema.TypeString, @@ -596,7 +598,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentVMDiskFileID, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentVMDiskSize: { Type: schema.TypeInt, @@ -695,7 +697,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Computed: true, - ValidateDiagFunc: getFileFormatValidator(), + ValidateDiagFunc: validator.FileFormat(), }, mkResourceVirtualEnvironmentVMEFIDiskType: { Type: schema.TypeString, @@ -875,7 +877,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentVMInitializationUserDataFileID, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentVMInitializationVendorDataFileID: { Type: schema.TypeString, @@ -883,7 +885,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentVMInitializationVendorDataFileID, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentVMInitializationNetworkDataFileID: { Type: schema.TypeString, @@ -891,7 +893,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentVMInitializationNetworkDataFileID, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentVMInitializationMetaDataFileID: { Type: schema.TypeString, @@ -899,7 +901,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentVMInitializationMetaDataFileID, - ValidateDiagFunc: getFileIDValidator(), + ValidateDiagFunc: validator.FileID(), }, mkResourceVirtualEnvironmentVMInitializationType: { Type: schema.TypeString, @@ -907,7 +909,7 @@ func VM() *schema.Resource { Optional: true, ForceNew: true, Default: dvResourceVirtualEnvironmentVMInitializationType, - ValidateDiagFunc: getCloudInitTypeValidator(), + ValidateDiagFunc: validator.CloudInitType(), }, }, }, @@ -985,7 +987,7 @@ func VM() *schema.Resource { Description: "The keyboard layout", Optional: true, Default: dvResourceVirtualEnvironmentVMKeyboardLayout, - ValidateDiagFunc: getKeyboardLayoutValidator(), + ValidateDiagFunc: validator.KeyboardLayout(), }, mkResourceVirtualEnvironmentVMMachine: { Type: schema.TypeString, @@ -1084,14 +1086,14 @@ func VM() *schema.Resource { Description: "The MAC address", Optional: true, Computed: true, - ValidateDiagFunc: getMACAddressValidator(), + ValidateDiagFunc: validator.MACAddress(), }, mkResourceVirtualEnvironmentVMNetworkDeviceModel: { Type: schema.TypeString, Description: "The model", Optional: true, Default: dvResourceVirtualEnvironmentVMNetworkDeviceModel, - ValidateDiagFunc: getNetworkDeviceModelValidator(), + ValidateDiagFunc: validator.NetworkDeviceModel(), }, mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit: { Type: schema.TypeFloat, @@ -1208,7 +1210,7 @@ func VM() *schema.Resource { Type: schema.TypeString, ValidateFunc: validation.StringIsNotEmpty, }, - DiffSuppressFunc: suppressIfListsAreEqualIgnoringOrder, + DiffSuppressFunc: structure.SuppressIfListsAreEqualIgnoringOrder, DiffSuppressOnRefresh: true, }, mkResourceVirtualEnvironmentVMTemplate: { @@ -1280,14 +1282,14 @@ func VM() *schema.Resource { Description: "The VGA memory in megabytes (4-512 MB)", Optional: true, Default: dvResourceVirtualEnvironmentVMVGAMemory, - ValidateDiagFunc: getVGAMemoryValidator(), + ValidateDiagFunc: validator.VGAMemory(), }, mkResourceVirtualEnvironmentVMVGAType: { Type: schema.TypeString, Description: "The VGA type", Optional: true, Default: dvResourceVirtualEnvironmentVMVGAType, - ValidateDiagFunc: getVGATypeValidator(), + ValidateDiagFunc: validator.VGAType(), }, }, }, @@ -1308,7 +1310,7 @@ func VM() *schema.Resource { Description: "The SCSI hardware type", Optional: true, Default: dvResourceVirtualEnvironmentVMSCSIHardware, - ValidateDiagFunc: getSCSIHardwareValidator(), + ValidateDiagFunc: validator.SCSIHardware(), }, }, CreateContext: vmCreate, @@ -1975,7 +1977,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) acpi := types.CustomBool(d.Get(mkResourceVirtualEnvironmentVMACPI).(bool)) - agentBlock, err := getSchemaBlock( + agentBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMAgent}, @@ -1998,7 +2000,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) bios := d.Get(mkResourceVirtualEnvironmentVMBIOS).(string) - cdromBlock, err := getSchemaBlock( + cdromBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMCDROM}, @@ -2020,7 +2022,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) cdromFileID = "cdrom" } - cpuBlock, err := getSchemaBlock( + cpuBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMCPU}, @@ -2088,7 +2090,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) pciDeviceObjects := vmGetHostPCIDeviceObjects(d) keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string) - memoryBlock, err := getSchemaBlock( + memoryBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMMemory}, @@ -2111,7 +2113,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string) - operatingSystem, err := getSchemaBlock( + operatingSystem, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMOperatingSystem}, @@ -2721,7 +2723,7 @@ func vmGetDiskDeviceObjects( ssd := types.CustomBool(block[mkResourceVirtualEnvironmentVMDiskSSD].(bool)) discard := block[mkResourceVirtualEnvironmentVMDiskDiscard].(string) - speedBlock, err := getSchemaBlock( + speedBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMDisk, mkResourceVirtualEnvironmentVMDiskSpeed}, @@ -3016,7 +3018,7 @@ func vmGetSerialDeviceValidator() schema.SchemaValidateDiagFunc { func vmGetVGADeviceObject(d *schema.ResourceData) (*vms.CustomVGADevice, error) { resource := VM() - vgaBlock, err := getSchemaBlock( + vgaBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMVGA}, @@ -4126,7 +4128,7 @@ func vmReadNetworkValues( if started { if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled { resource := VM() - agentBlock, err := getSchemaBlock( + agentBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMAgent}, @@ -4453,7 +4455,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D // Prepare the new agent configuration. if d.HasChange(mkResourceVirtualEnvironmentVMAgent) { - agentBlock, err := getSchemaBlock( + agentBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMAgent}, @@ -4513,7 +4515,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D // Prepare the new CD-ROM configuration. if d.HasChange(mkResourceVirtualEnvironmentVMCDROM) { - cdromBlock, err := getSchemaBlock( + cdromBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMCDROM}, @@ -4564,7 +4566,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D // Prepare the new CPU configuration. if d.HasChange(mkResourceVirtualEnvironmentVMCPU) { - cpuBlock, err := getSchemaBlock( + cpuBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMCPU}, @@ -4726,7 +4728,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D // Prepare the new memory configuration. if d.HasChange(mkResourceVirtualEnvironmentVMMemory) { - memoryBlock, err := getSchemaBlock( + memoryBlock, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMMemory}, @@ -4775,7 +4777,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D // Prepare the new operating system configuration. if d.HasChange(mkResourceVirtualEnvironmentVMOperatingSystem) { - operatingSystem, err := getSchemaBlock( + operatingSystem, err := structure.GetSchemaBlock( resource, d, []string{mkResourceVirtualEnvironmentVMOperatingSystem}, @@ -5112,3 +5114,138 @@ func vmDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D return nil } + +func diskDigitPrefix(s string) string { + for i, r := range s { + if unicode.IsDigit(r) { + return s[:i] + } + } + + return s +} + +func getDiskInfo(resp *vms.GetResponseData, d *schema.ResourceData) map[string]*vms.CustomStorageDevice { + currentDisk := d.Get(mkResourceVirtualEnvironmentVMDisk) + + currentDiskList := currentDisk.([]interface{}) + currentDiskMap := map[string]map[string]interface{}{} + + for _, v := range currentDiskList { + diskMap := v.(map[string]interface{}) + diskInterface := diskMap[mkResourceVirtualEnvironmentVMDiskInterface].(string) + + currentDiskMap[diskInterface] = diskMap + } + + storageDevices := map[string]*vms.CustomStorageDevice{} + + storageDevices["ide0"] = resp.IDEDevice0 + storageDevices["ide1"] = resp.IDEDevice1 + storageDevices["ide2"] = resp.IDEDevice2 + storageDevices["ide3"] = resp.IDEDevice3 + + storageDevices["sata0"] = resp.SATADevice0 + storageDevices["sata1"] = resp.SATADevice1 + storageDevices["sata2"] = resp.SATADevice2 + storageDevices["sata3"] = resp.SATADevice3 + storageDevices["sata4"] = resp.SATADevice4 + storageDevices["sata5"] = resp.SATADevice5 + + storageDevices["scsi0"] = resp.SCSIDevice0 + storageDevices["scsi1"] = resp.SCSIDevice1 + storageDevices["scsi2"] = resp.SCSIDevice2 + storageDevices["scsi3"] = resp.SCSIDevice3 + storageDevices["scsi4"] = resp.SCSIDevice4 + storageDevices["scsi5"] = resp.SCSIDevice5 + storageDevices["scsi6"] = resp.SCSIDevice6 + storageDevices["scsi7"] = resp.SCSIDevice7 + storageDevices["scsi8"] = resp.SCSIDevice8 + storageDevices["scsi9"] = resp.SCSIDevice9 + storageDevices["scsi10"] = resp.SCSIDevice10 + storageDevices["scsi11"] = resp.SCSIDevice11 + storageDevices["scsi12"] = resp.SCSIDevice12 + storageDevices["scsi13"] = resp.SCSIDevice13 + + storageDevices["virtio0"] = resp.VirtualIODevice0 + storageDevices["virtio1"] = resp.VirtualIODevice1 + storageDevices["virtio2"] = resp.VirtualIODevice2 + storageDevices["virtio3"] = resp.VirtualIODevice3 + storageDevices["virtio4"] = resp.VirtualIODevice4 + storageDevices["virtio5"] = resp.VirtualIODevice5 + storageDevices["virtio6"] = resp.VirtualIODevice6 + storageDevices["virtio7"] = resp.VirtualIODevice7 + storageDevices["virtio8"] = resp.VirtualIODevice8 + storageDevices["virtio9"] = resp.VirtualIODevice9 + storageDevices["virtio10"] = resp.VirtualIODevice10 + storageDevices["virtio11"] = resp.VirtualIODevice11 + storageDevices["virtio12"] = resp.VirtualIODevice12 + storageDevices["virtio13"] = resp.VirtualIODevice13 + storageDevices["virtio14"] = resp.VirtualIODevice14 + storageDevices["virtio15"] = resp.VirtualIODevice15 + + for k, v := range storageDevices { + if v != nil { + if currentDiskMap[k] != nil { + if currentDiskMap[k][mkResourceVirtualEnvironmentVMDiskFileID] != nil { + fileID := currentDiskMap[k][mkResourceVirtualEnvironmentVMDiskFileID].(string) + v.FileID = &fileID + } + } + // defensive copy of the loop variable + iface := k + v.Interface = &iface + } + } + + return storageDevices +} + +// getDiskDatastores returns a list of the used datastores in a VM. +func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string { + storageDevices := getDiskInfo(vm, d) + datastoresSet := map[string]int{} + + for _, diskInfo := range storageDevices { + // Ignore empty storage devices and storage devices (like ide) which may not have any media mounted + if diskInfo == nil || diskInfo.FileVolume == "none" { + continue + } + + fileIDParts := strings.Split(diskInfo.FileVolume, ":") + datastoresSet[fileIDParts[0]] = 1 + } + + if vm.EFIDisk != nil { + fileIDParts := strings.Split(vm.EFIDisk.FileVolume, ":") + datastoresSet[fileIDParts[0]] = 1 + } + + datastores := []string{} + for datastore := range datastoresSet { + datastores = append(datastores, datastore) + } + + return datastores +} + +func getPCIInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomPCIDevice { + pciDevices := map[string]*vms.CustomPCIDevice{} + + pciDevices["hostpci0"] = resp.PCIDevice0 + pciDevices["hostpci1"] = resp.PCIDevice1 + pciDevices["hostpci2"] = resp.PCIDevice2 + pciDevices["hostpci3"] = resp.PCIDevice3 + + return pciDevices +} + +func parseImportIDWithNodeName(id string) (string, string, error) { + nodeName, id, found := strings.Cut(id, "/") + + if !found { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected node/id", id) + } + + return nodeName, id, nil +} diff --git a/proxmoxtf/resource/vm_test.go b/proxmoxtf/resource/vm_test.go index 03c989d9..a3f2eb80 100644 --- a/proxmoxtf/resource/vm_test.go +++ b/proxmoxtf/resource/vm_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/require" "github.com/bpg/terraform-provider-proxmox/proxmoxtf/test" ) @@ -437,3 +438,38 @@ func TestVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMVGAType: schema.TypeString, }) } + +func Test_parseImportIDWIthNodeName(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value string + valid bool + expectedNodeName string + expectedID string + }{ + {"empty", "", false, "", ""}, + {"missing slash", "invalid", false, "", ""}, + {"valid", "host/id", true, "host", "id"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require := require.New(t) + + nodeName, id, err := parseImportIDWithNodeName(tt.value) + + if !tt.valid { + require.Error(err) + return + } + + require.Nil(err) + require.Equal(tt.expectedNodeName, nodeName) + require.Equal(tt.expectedID, id) + }) + } +} diff --git a/proxmoxtf/structure/schema.go b/proxmoxtf/structure/schema.go new file mode 100644 index 00000000..b9f459a4 --- /dev/null +++ b/proxmoxtf/structure/schema.go @@ -0,0 +1,122 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package structure + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// MergeSchema merges the map[string]*schema.Schema from src into dst. Safety +// against conflicts is enforced by panicking. +func MergeSchema(dst, src map[string]*schema.Schema) { + for k, v := range src { + if _, ok := dst[k]; ok { + panic(fmt.Errorf("conflicting schema key: %s", k)) + } + + dst[k] = v + } +} + +// GetSchemaBlock returns a map[string]interface{} of a nested resource by key(s) from a schema.ResourceData. +func GetSchemaBlock( + r *schema.Resource, + d *schema.ResourceData, + 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, fmt.Errorf("failed to get default value for %s: %w", strings.Join(k, "."), err) + } + + list = listDefault.([]interface{}) + } + } + + if len(list) > i { + resourceBlock = list[i].(map[string]interface{}) + } + + return resourceBlock, nil +} + +// SuppressIfListsAreEqualIgnoringOrder is a customdiff.SuppressionFunc that suppresses +// changes to a list if the old and new lists are equal, ignoring the order of the +// elements. +// It will be called for each list item, so it is not super efficient. It is +// recommended to use it only for small lists. +// Ref: https://github.com/hashicorp/terraform-plugin-sdk/issues/477 +func SuppressIfListsAreEqualIgnoringOrder(key, _, _ string, d *schema.ResourceData) bool { + // the key is a path to the list item, not the list itself, e.g. "tags.0" + lastDotIndex := strings.LastIndex(key, ".") + if lastDotIndex != -1 { + key = key[:lastDotIndex] + } + + oldData, newData := d.GetChange(key) + if oldData == nil || newData == nil { + return false + } + + oldArray := oldData.([]interface{}) + newArray := newData.([]interface{}) + + if len(oldArray) != len(newArray) { + return false + } + + oldEvents := make([]string, len(oldArray)) + newEvents := make([]string, len(newArray)) + + for i, oldEvt := range oldArray { + oldEvents[i] = fmt.Sprint(oldEvt) + } + + for j, newEvt := range newArray { + newEvents[j] = fmt.Sprint(newEvt) + } + + sort.Strings(oldEvents) + sort.Strings(newEvents) + + return reflect.DeepEqual(oldEvents, newEvents) +} diff --git a/proxmoxtf/structure/test.go b/proxmoxtf/structure/test.go deleted file mode 100644 index 8b0bad08..00000000 --- a/proxmoxtf/structure/test.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package structure - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// AssertComputedAttributes asserts that the given keys are present in the schema and are computed. -func AssertComputedAttributes(t *testing.T, s map[string]*schema.Schema, keys []string) { - t.Helper() - - for _, v := range keys { - require.NotNil(t, s[v], "Error in Schema: Missing definition for \"%s\"", v) - assert.True(t, s[v].Computed, "Error in Schema: Attribute \"%s\" is not computed", v) - } -} - -// AssertNestedSchemaExistence asserts that the given key is present in the schema and is a nested schema. -func AssertNestedSchemaExistence(t *testing.T, s map[string]*schema.Schema, key string) *schema.Resource { - t.Helper() - - sh, ok := s[key].Elem.(*schema.Resource) - - if !ok { - t.Fatalf("Error in Schema: Missing nested schema for \"%s\"", key) - - return nil - } - - return sh -} - -// AssertOptionalArguments asserts that the given keys are present in the schema and are optional. -func AssertOptionalArguments(t *testing.T, s map[string]*schema.Schema, keys []string) { - t.Helper() - - for _, v := range keys { - require.NotNil(t, s[v], "Error in Schema: Missing definition for \"%s\"", v) - assert.True(t, s[v].Optional, "Error in Schema: Argument \"%s\" is not optional", v) - } -} - -// AssertRequiredArguments asserts that the given keys are present in the schema and are required. -func AssertRequiredArguments(t *testing.T, s map[string]*schema.Schema, keys []string) { - t.Helper() - - for _, v := range keys { - require.NotNil(t, s[v], "Error in Schema: Missing definition for \"%s\"", v) - assert.True(t, s[v].Required, "Error in Schema: Argument \"%s\" is not required", v) - } -} - -// AssertValueTypes asserts that the given keys are present in the schema and are of the given type. -func AssertValueTypes(t *testing.T, s map[string]*schema.Schema, f map[string]schema.ValueType) { - t.Helper() - - for fn, ft := range f { - require.NotNil(t, s[fn], "Error in Schema: Missing definition for \"%s\"", fn) - assert.Equal(t, ft, s[fn].Type, "Error in Schema: Argument or attribute \"%s\" is not of type \"%v\"", fn, ft) - } -} diff --git a/proxmoxtf/structure/tools.go b/proxmoxtf/structure/tools.go deleted file mode 100644 index d89c58c5..00000000 --- a/proxmoxtf/structure/tools.go +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package structure - -import ( - "fmt" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -// MergeSchema merges the map[string]*schema.Schema from src into dst. Safety -// against conflicts is enforced by panicking. -func MergeSchema(dst, src map[string]*schema.Schema) { - for k, v := range src { - if _, ok := dst[k]; ok { - panic(fmt.Errorf("conflicting schema key: %s", k)) - } - - dst[k] = v - } -} diff --git a/proxmoxtf/test/test_utils.go b/proxmoxtf/test/utils.go similarity index 100% rename from proxmoxtf/test/test_utils.go rename to proxmoxtf/test/utils.go