Skip to content

Commit 53cce6f

Browse files
fix(vm,lxc): add name validation (#2631)
Signed-off-by: Stanislav Shamilov <shamilovstas@protonmail.com> Signed-off-by: Pavel Boldyrev <pavel@bpg.sh> Co-authored-by: Pavel Boldyrev <pavel@bpg.sh>
1 parent 9af341a commit 53cce6f

File tree

10 files changed

+92
-11
lines changed

10 files changed

+92
-11
lines changed

docs/resources/virtual_environment_container.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ output "ubuntu_container_public_key" {
165165
The `server` attribute is deprecated and will be removed in a future release. Please use
166166
the `servers` attribute instead.
167167
- `servers` - (Optional) The list of DNS servers.
168-
- `hostname` - (Optional) The hostname.
168+
- `hostname` - (Optional) The hostname. Must be a valid DNS name.
169169
- `ip_config` - (Optional) The IP configuration (one block per network
170170
device).
171171
- `ipv4` - (Optional) The IPv4 configuration.

docs/resources/virtual_environment_vm.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ output "ubuntu_vm_public_key" {
483483

484484
- `migrate` - (Optional) Migrate the VM on node change instead of re-creating
485485
it (defaults to `false`).
486-
- `name` - (Optional) The virtual machine name.
486+
- `name` - (Optional) The virtual machine name. Must be a valid DNS name.
487487
- `network_device` - (Optional) A network device (multiple blocks supported).
488488
- `bridge` - (Optional) The name of the network bridge (defaults to `vmbr0`).
489489
- `disconnected` - (Optional) Whether to disconnect the network device from the network (defaults to `false`).

fwprovider/nodes/vm/resource_schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (r *Resource) Schema(
6060
Optional: true,
6161
Validators: []validator.String{
6262
stringvalidator.RegexMatches(
63-
regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$`),
63+
regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*$`),
6464
"must be a valid DNS name",
6565
),
6666
},

fwprovider/nodes/vm/resource_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ func TestAccResourceVM(t *testing.T) {
5959
}`),
6060
ExpectError: regexp.MustCompile(`name must be a valid DNS name`),
6161
}}},
62+
{"set a FQDN VM name", []resource.TestStep{{
63+
Config: te.RenderConfig(`
64+
resource "proxmox_virtual_environment_vm2" "test_vm" {
65+
node_name = "{{.NodeName}}"
66+
name = "vm.example.com"
67+
}`),
68+
Check: test.ResourceAttributes("proxmox_virtual_environment_vm2.test_vm", map[string]string{
69+
"name": "vm.example.com",
70+
}),
71+
}}},
6272
{"set, update, import with primitive fields", []resource.TestStep{
6373
{
6474
Config: te.RenderConfig(`

fwprovider/test/resource_container_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"context"
1313
"fmt"
1414
"math/rand"
15+
"regexp"
1516
"strings"
1617
"testing"
1718
"time"
@@ -883,6 +884,18 @@ func TestAccResourceContainerHostname(t *testing.T) {
883884
resource.ParallelTest(t, resource.TestCase{
884885
ProtoV6ProviderFactories: te.AccProviders,
885886
Steps: []resource.TestStep{
887+
{
888+
Config: te.RenderConfig(`
889+
resource "proxmox_virtual_environment_container" "test_container" {
890+
node_name = "pve"
891+
initialization {
892+
hostname = ".world"
893+
}
894+
}
895+
`),
896+
ExpectError: regexp.MustCompile(`invalid value for hostname \(must be a valid DNS name\)`),
897+
PlanOnly: true,
898+
},
886899
{
887900
Config: te.RenderConfig(`
888901
resource "proxmox_virtual_environment_container" "test_container" {

fwprovider/test/resource_vm_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ func TestAccResourceVM(t *testing.T) {
8888
}`),
8989
ExpectError: regexp.MustCompile(`expected "node_name" to not be an empty string, got `),
9090
}}},
91+
{"name", []resource.TestStep{{
92+
Config: te.RenderConfig(`
93+
resource "proxmox_virtual_environment_vm" "vm" {
94+
node_name = "pve"
95+
name = ".hello"
96+
}`),
97+
ExpectError: regexp.MustCompile(`invalid value for name \(must be a valid DNS name\)`),
98+
PlanOnly: true,
99+
}}},
91100
{"protection", []resource.TestStep{
92101
{
93102
Config: te.RenderConfig(`

proxmoxtf/resource/container/container.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -519,10 +519,11 @@ func Container() *schema.Resource {
519519
DiffSuppressFunc: skipDnsDiffIfEmpty,
520520
},
521521
mkInitializationHostname: {
522-
Type: schema.TypeString,
523-
Description: "The hostname",
524-
Optional: true,
525-
Default: dvInitializationHostname,
522+
Type: schema.TypeString,
523+
Description: "The hostname. Must be a valid DNS name.",
524+
Optional: true,
525+
Default: dvInitializationHostname,
526+
ValidateDiagFunc: resource.HostnameValidator(),
526527
},
527528
mkInitializationIPConfig: {
528529
Type: schema.TypeList,

proxmoxtf/resource/vm/validators.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,18 @@ func RangeSemicolonValidator() schema.SchemaValidateDiagFunc {
403403
),
404404
)
405405
}
406+
407+
// HostnameValidator is a scheme validation function for virtual machine name. According to Proxmox documentation,
408+
// it must be a valid DNS name.
409+
// Regexp is based on Proxmox validation at https://git.proxmox.com/?p=pve-common.git;a=blob;f=src/PVE/JSONSchema.pm
410+
func HostnameValidator() schema.SchemaValidateDiagFunc {
411+
return validation.ToDiagFunc(
412+
validation.Any(
413+
validation.StringIsEmpty,
414+
validation.StringMatch(
415+
regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*$`),
416+
"must be a valid DNS name",
417+
),
418+
),
419+
)
420+
}

proxmoxtf/resource/vm/validators_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package resource
99
import (
1010
"testing"
1111

12+
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314
)
1415

@@ -77,3 +78,34 @@ func TestMachineType(t *testing.T) {
7778
})
7879
}
7980
}
81+
82+
func TestVmHostname(t *testing.T) {
83+
t.Parallel()
84+
85+
tests := []struct {
86+
name string
87+
value string
88+
valid bool
89+
}{
90+
{"empty", "", true},
91+
{"underscores", "my_name", false},
92+
{"trailing dot", "my-name.com.", false},
93+
{"starts with alphanumeric", "-my-name.com", false},
94+
{"ends with alphanumeric", "my-name.com!", false},
95+
{"single letter", "a", true},
96+
{"domain name", "my-name.com", true},
97+
{"multi domain", "my-name.com.edu.net.xyz.dev", true},
98+
}
99+
100+
for _, tt := range tests {
101+
t.Run(tt.name, func(t *testing.T) {
102+
t.Parallel()
103+
104+
f := HostnameValidator()
105+
res := f(tt.value, nil)
106+
107+
valid := !res.HasError()
108+
assert.Equal(t, tt.valid, valid)
109+
})
110+
}
111+
}

proxmoxtf/resource/vm/vm.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,10 +1210,11 @@ func VM() *schema.Resource {
12101210
MinItems: 0,
12111211
},
12121212
mkName: {
1213-
Type: schema.TypeString,
1214-
Description: "The name",
1215-
Optional: true,
1216-
Default: dvName,
1213+
Type: schema.TypeString,
1214+
Description: "The name of the VM. Must be a valid DNS name.",
1215+
Optional: true,
1216+
Default: dvName,
1217+
ValidateDiagFunc: HostnameValidator(),
12171218
},
12181219
mkNodeName: {
12191220
Type: schema.TypeString,

0 commit comments

Comments
 (0)