0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-06 14:03:58 +00:00

Continue work on container resource

This commit is contained in:
Dan Petersen 2020-01-02 11:45:46 +01:00
parent 6dac302a01
commit f6c4ad2af7
13 changed files with 907 additions and 91 deletions

View File

@ -0,0 +1,40 @@
resource "proxmox_virtual_environment_container" "example" {
description = "Managed by Terraform"
initialization {
dns {
server = "1.1.1.1"
}
hostname = "terraform-provider-proxmox-example-lxc"
ip_config {
ipv4 {
address = "dhcp"
}
}
user_account {
keys = ["${trimspace(tls_private_key.example.public_key_openssh)}"]
password = "example"
}
}
network_interface {
name = "veth0"
}
node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}"
operating_system {
template_file_id = "${proxmox_virtual_environment_file.ubuntu_container_template.id}"
type = "ubuntu"
}
pool_id = "${proxmox_virtual_environment_pool.example.id}"
vm_id = 2039
}
output "resource_proxmox_virtual_environment_container_example_id" {
value = "${proxmox_virtual_environment_container.example.id}"
}

View File

@ -1,12 +1,6 @@
resource "proxmox_virtual_environment_file" "ubuntu_cloud_image" {
content_type = "iso"
datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}"
node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}"
source_file {
path = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img"
}
}
#===============================================================================
# Cloud Config (cloud-init)
#===============================================================================
resource "proxmox_virtual_environment_file" "cloud_config" {
content_type = "snippets"
@ -37,6 +31,20 @@ users:
}
}
#===============================================================================
# Ubuntu Cloud Image
#===============================================================================
resource "proxmox_virtual_environment_file" "ubuntu_cloud_image" {
content_type = "iso"
datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}"
node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}"
source_file {
path = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img"
}
}
output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_content_type" {
value = "${proxmox_virtual_environment_file.ubuntu_cloud_image.content_type}"
}
@ -72,3 +80,53 @@ output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_node_name"
output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_source_file" {
value = "${proxmox_virtual_environment_file.ubuntu_cloud_image.source_file}"
}
#===============================================================================
# Ubuntu Container Template
#===============================================================================
resource "proxmox_virtual_environment_file" "ubuntu_container_template" {
content_type = "vztmpl"
datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}"
node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}"
source_file {
path = "http://download.proxmox.com/images/system/ubuntu-18.04-standard_18.04.1-1_amd64.tar.gz"
}
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_content_type" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.content_type}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_datastore_id" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.datastore_id}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_file_modification_date" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.file_modification_date}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_file_name" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.file_name}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_file_size" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.file_size}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_file_tag" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.file_tag}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_id" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.id}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_node_name" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.node_name}"
}
output "resource_proxmox_virtual_environment_file_ubuntu_container_template_source_file" {
value = "${proxmox_virtual_environment_file.ubuntu_container_template.source_file}"
}

View File

@ -58,21 +58,6 @@ resource "proxmox_virtual_environment_vm" "example" {
}
}
resource "local_file" "example_ssh_private_key" {
filename = "${path.module}/autogenerated/id_rsa"
sensitive_content = "${tls_private_key.example.private_key_pem}"
}
resource "local_file" "example_ssh_public_key" {
filename = "${path.module}/autogenerated/id_rsa.pub"
sensitive_content = "${tls_private_key.example.public_key_openssh}"
}
resource "tls_private_key" "example" {
algorithm = "RSA"
rsa_bits = 2048
}
output "resource_proxmox_virtual_environment_vm_example_id" {
value = "${proxmox_virtual_environment_vm.example.id}"
}

14
example/ssh.tf Normal file
View File

@ -0,0 +1,14 @@
resource "local_file" "example_ssh_private_key" {
filename = "${path.module}/autogenerated/id_rsa"
sensitive_content = "${tls_private_key.example.private_key_pem}"
}
resource "local_file" "example_ssh_public_key" {
filename = "${path.module}/autogenerated/id_rsa.pub"
sensitive_content = "${tls_private_key.example.public_key_openssh}"
}
resource "tls_private_key" "example" {
algorithm = "RSA"
rsa_bits = 2048
}

1
go.mod
View File

@ -8,4 +8,5 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/pkg/sftp v1.10.1
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55
)

View File

@ -110,3 +110,33 @@ func (c *VirtualEnvironmentClient) WaitForContainerState(nodeName string, vmID i
return fmt.Errorf("Timeout while waiting for container \"%d\" to enter the state \"%s\"", vmID, state)
}
// WaitForContainerLock waits for a container lock to be released.
func (c *VirtualEnvironmentClient) WaitForContainerLock(nodeName string, vmID int, timeout int, delay int) error {
timeDelay := int64(delay)
timeMax := float64(timeout)
timeStart := time.Now()
timeElapsed := timeStart.Sub(timeStart)
for timeElapsed.Seconds() < timeMax {
if int64(timeElapsed.Seconds())%timeDelay == 0 {
data, err := c.GetContainerStatus(nodeName, vmID)
if err != nil {
return err
}
if data.Lock == nil || *data.Lock == "" {
return nil
}
time.Sleep(1 * time.Second)
}
time.Sleep(200 * time.Millisecond)
timeElapsed = time.Now().Sub(timeStart)
}
return fmt.Errorf("Timeout while waiting for container \"%d\" to become unlocked", vmID)
}

View File

@ -34,7 +34,7 @@ type VirtualEnvironmentContainerCreateRequestBody struct {
Lock *string `json:"lock,omitempty" url:"lock,omitempty,int"`
MountPoints VirtualEnvironmentContainerCustomMountPointArray `json:"mp,omitempty" url:"mp,omitempty,numbered"`
NetworkInterfaces VirtualEnvironmentContainerCustomNetworkInterfaceArray `json:"net,omitempty" url:"net,omitempty,numbered"`
OSTemplateFileVolume string `json:"ostemplate" url:"ostemplate"`
OSTemplateFileVolume *string `json:"ostemplate,omitempty" url:"ostemplate,omitempty"`
OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"`
Password *string `json:"password,omitempty" url:"password,omitempty"`
PoolID *string `json:"pool,omitempty" url:"pool,omitempty"`
@ -51,7 +51,7 @@ type VirtualEnvironmentContainerCreateRequestBody struct {
TTY *int `json:"tty,omitempty" url:"tty,omitempty"`
Unique *CustomBool `json:"unique,omitempty" url:"unique,omitempty,int"`
Unprivileged *CustomBool `json:"unprivileged,omitempty" url:"unprivileged,omitempty,int"`
VMID int `json:"vmid" url:"vmid"`
VMID *int `json:"vmid,omitempty" url:"vmid,omitempty"`
}
// VirtualEnvironmentContainerCustomFeatures contains the values for the "features" property.
@ -66,7 +66,7 @@ type VirtualEnvironmentContainerCustomFeatures struct {
type VirtualEnvironmentContainerCustomMountPoint struct {
ACL *CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"`
Backup *CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
DiskSize *int `json:"size,omitempty" url:"size,omitempty"`
DiskSize *string `json:"size,omitempty" url:"size,omitempty"`
Enabled bool `json:"-" url:"-"`
MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"`
MountPoint string `json:"mp" url:"mp"`
@ -92,7 +92,7 @@ type VirtualEnvironmentContainerCustomNetworkInterface struct {
MACAddress *string `json:"hwaddr,omitempty" url:"hwaddr,omitempty"`
MTU *int `json:"mtu,omitempty" url:"mtu,omitempty"`
Name string `json:"name" url:"name"`
RateLimit *int `json:"rate,omitempty" url:"rate,omitempty"`
RateLimit *float64 `json:"rate,omitempty" url:"rate,omitempty"`
Tag *int `json:"tag,omitempty" url:"tag,omitempty"`
Trunks *[]int `json:"trunks,omitempty" url:"trunks,omitempty"`
Type *string `json:"type,omitempty" url:"type,omitempty"`
@ -104,7 +104,7 @@ type VirtualEnvironmentContainerCustomNetworkInterfaceArray []VirtualEnvironment
// VirtualEnvironmentContainerCustomRootFS contains the values for the "rootfs" property.
type VirtualEnvironmentContainerCustomRootFS struct {
ACL *CustomBool `json:"acl,omitempty" url:"acl,omitempty,int"`
DiskSize *int `json:"size,omitempty" url:"size,omitempty"`
DiskSize *string `json:"size,omitempty" url:"size,omitempty"`
MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"`
Quota *CustomBool `json:"quota,omitempty" url:"quota,omitempty,int"`
ReadOnly *CustomBool `json:"ro,omitempty" url:"ro,omitempty,int"`
@ -177,16 +177,16 @@ type VirtualEnvironmentContainerGetStatusResponseBody struct {
// VirtualEnvironmentContainerGetStatusResponseData contains the data from a container get status response.
type VirtualEnvironmentContainerGetStatusResponseData struct {
CPUCount *float64 `json:"cpus,omitempty"`
Lock *string `json:"lock,omitempty"`
MemoryAllocation *int `json:"maxmem,omitempty"`
Name *string `json:"name,omitempty"`
RootDiskSize *int `json:"maxdisk,omitempty"`
Status string `json:"status,omitempty"`
SwapAllocation *int `json:"maxswap,omitempty"`
Tags *string `json:"tags,omitempty"`
Uptime *int `json:"uptime,omitempty"`
VMID string `json:"vmid,omitempty"`
CPUCount *float64 `json:"cpus,omitempty"`
Lock *string `json:"lock,omitempty"`
MemoryAllocation *int `json:"maxmem,omitempty"`
Name *string `json:"name,omitempty"`
RootDiskSize *interface{} `json:"maxdisk,omitempty"`
Status string `json:"status,omitempty"`
SwapAllocation *int `json:"maxswap,omitempty"`
Tags *string `json:"tags,omitempty"`
Uptime *int `json:"uptime,omitempty"`
VMID string `json:"vmid,omitempty"`
}
// VirtualEnvironmentContainerRebootRequestBody contains the body for a container reboot request.
@ -265,7 +265,7 @@ func (r VirtualEnvironmentContainerCustomMountPoint) EncodeValues(key string, v
}
if r.DiskSize != nil {
values = append(values, fmt.Sprintf("size=%d", *r.DiskSize))
values = append(values, fmt.Sprintf("size=%s", *r.DiskSize))
}
if r.MountOptions != nil {
@ -317,6 +317,15 @@ func (r VirtualEnvironmentContainerCustomMountPoint) EncodeValues(key string, v
return nil
}
// EncodeValues converts a VirtualEnvironmentContainerCustomMountPointArray array to multiple URL values.
func (r VirtualEnvironmentContainerCustomMountPointArray) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
}
return nil
}
// EncodeValues converts a VirtualEnvironmentContainerCustomNetworkInterface struct to a URL vlaue.
func (r VirtualEnvironmentContainerCustomNetworkInterface) EncodeValues(key string, v *url.Values) error {
values := []string{}
@ -360,7 +369,7 @@ func (r VirtualEnvironmentContainerCustomNetworkInterface) EncodeValues(key stri
values = append(values, fmt.Sprintf("name=%s", r.Name))
if r.RateLimit != nil {
values = append(values, fmt.Sprintf("rate=%d", *r.RateLimit))
values = append(values, fmt.Sprintf("rate=%.2f", *r.RateLimit))
}
if r.Tag != nil {
@ -388,6 +397,15 @@ func (r VirtualEnvironmentContainerCustomNetworkInterface) EncodeValues(key stri
return nil
}
// EncodeValues converts a VirtualEnvironmentContainerCustomNetworkInterfaceArray array to multiple URL values.
func (r VirtualEnvironmentContainerCustomNetworkInterfaceArray) EncodeValues(key string, v *url.Values) error {
for i, d := range r {
d.EncodeValues(fmt.Sprintf("%s%d", key, i), v)
}
return nil
}
// EncodeValues converts a VirtualEnvironmentContainerCustomRootFS struct to a URL vlaue.
func (r VirtualEnvironmentContainerCustomRootFS) EncodeValues(key string, v *url.Values) error {
values := []string{}
@ -401,7 +419,7 @@ func (r VirtualEnvironmentContainerCustomRootFS) EncodeValues(key string, v *url
}
if r.DiskSize != nil {
values = append(values, fmt.Sprintf("size=%d", *r.DiskSize))
values = append(values, fmt.Sprintf("size=%s", *r.DiskSize))
}
if r.MountOptions != nil {
@ -453,7 +471,7 @@ func (r VirtualEnvironmentContainerCustomRootFS) EncodeValues(key string, v *url
// EncodeValues converts a VirtualEnvironmentContainerCustomSSHKeys array to a URL vlaue.
func (r VirtualEnvironmentContainerCustomSSHKeys) EncodeValues(key string, v *url.Values) error {
v.Add(key, strings.ReplaceAll(url.QueryEscape(strings.Join(r, "\n")), "+", "%20"))
v.Add(key, strings.Join(r, "\n"))
return nil
}
@ -538,7 +556,7 @@ func (r *VirtualEnvironmentContainerCustomMountPoint) UnmarshalJSON(b []byte) er
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Volume = v[1]
r.Volume = v[0]
} else if len(v) == 2 {
switch v[0] {
case "acl":
@ -570,13 +588,7 @@ func (r *VirtualEnvironmentContainerCustomMountPoint) UnmarshalJSON(b []byte) er
bv := CustomBool(v[1] == "1")
r.Shared = &bv
case "size":
iv, err := strconv.Atoi(v[1])
if err != nil {
return err
}
r.DiskSize = &iv
r.DiskSize = &v[1]
}
}
}
@ -600,7 +612,7 @@ func (r *VirtualEnvironmentContainerCustomNetworkInterface) UnmarshalJSON(b []by
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Name = v[1]
r.Name = v[0]
} else if len(v) == 2 {
switch v[0] {
case "bridge":
@ -629,13 +641,13 @@ func (r *VirtualEnvironmentContainerCustomNetworkInterface) UnmarshalJSON(b []by
case "name":
r.Name = v[1]
case "rate":
iv, err := strconv.Atoi(v[1])
fv, err := strconv.ParseFloat(v[1], 64)
if err != nil {
return err
}
r.RateLimit = &iv
r.RateLimit = &fv
case "tag":
iv, err := strconv.Atoi(v[1])
@ -687,7 +699,7 @@ func (r *VirtualEnvironmentContainerCustomRootFS) UnmarshalJSON(b []byte) error
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Volume = v[1]
r.Volume = v[0]
} else if len(v) == 2 {
switch v[0] {
case "acl":
@ -714,13 +726,7 @@ func (r *VirtualEnvironmentContainerCustomRootFS) UnmarshalJSON(b []byte) error
bv := CustomBool(v[1] == "1")
r.Shared = &bv
case "size":
iv, err := strconv.Atoi(v[1])
if err != nil {
return err
}
r.DiskSize = &iv
r.DiskSize = &v[1]
}
}
}

View File

@ -46,6 +46,7 @@ func Provider() *schema.Provider {
},
ResourcesMap: map[string]*schema.Resource{
"proxmox_virtual_environment_certificate": resourceVirtualEnvironmentCertificate(),
"proxmox_virtual_environment_container": resourceVirtualEnvironmentContainer(),
"proxmox_virtual_environment_dns": resourceVirtualEnvironmentDNS(),
"proxmox_virtual_environment_file": resourceVirtualEnvironmentFile(),
"proxmox_virtual_environment_group": resourceVirtualEnvironmentGroup(),

View File

@ -5,6 +5,7 @@
package proxmoxtf
import (
"fmt"
"strconv"
"strings"
@ -77,7 +78,7 @@ const (
mkResourceVirtualEnvironmentContainerMemory = "memory"
mkResourceVirtualEnvironmentContainerMemoryDedicated = "dedicated"
mkResourceVirtualEnvironmentContainerMemorySwap = "swap"
mkResourceVirtualEnvironmentContainerNetworkInterface = "network_device"
mkResourceVirtualEnvironmentContainerNetworkInterface = "network_interface"
mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "bridge"
mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = "enabled"
mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress = "mac_address"
@ -324,7 +325,7 @@ func resourceVirtualEnvironmentContainer() *schema.Resource {
Sensitive: true,
Default: dvResourceVirtualEnvironmentContainerInitializationUserAccountPassword,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return strings.ReplaceAll(old, "*", "") == ""
return len(old) > 0 && strings.ReplaceAll(old, "*", "") == ""
},
},
},
@ -459,7 +460,7 @@ func resourceVirtualEnvironmentContainer() *schema.Resource {
},
mkResourceVirtualEnvironmentContainerPoolID: {
Type: schema.TypeString,
Description: "The ID of the pool to assign the virtual machine to",
Description: "The ID of the pool to assign the container to",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerPoolID,
@ -576,7 +577,7 @@ func resourceVirtualEnvironmentContainerCreate(d *schema.ResourceData, m interfa
initializationUserAccountBlock := initializationUserAccount[0].(map[string]interface{})
keys := initializationUserAccountBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccountKeys].([]interface{})
initializationUserAccountKeys := make(proxmox.VirtualEnvironmentContainerCustomSSHKeys, len(keys))
initializationUserAccountKeys = make(proxmox.VirtualEnvironmentContainerCustomSSHKeys, len(keys))
for ki, kv := range keys {
initializationUserAccountKeys[ki] = kv.(string)
@ -606,7 +607,7 @@ func resourceVirtualEnvironmentContainerCreate(d *schema.ResourceData, m interfa
enabled := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled].(bool)
macAddress := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress].(string)
name := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceName].(string)
rateLimit := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit].(int)
rateLimit := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit].(float64)
vlanID := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID].(int)
if bridge != "" {
@ -637,9 +638,7 @@ func resourceVirtualEnvironmentContainerCreate(d *schema.ResourceData, m interfa
networkInterfaceObject.MACAddress = &macAddress
}
if name != "" {
networkInterfaceObject.Name = name
}
networkInterfaceObject.Name = name
if rateLimit != 0 {
networkInterfaceObject.RateLimit = &rateLimit
@ -676,20 +675,23 @@ func resourceVirtualEnvironmentContainerCreate(d *schema.ResourceData, m interfa
}
// Attempt to create the resource using the retrieved values.
datastoreID := "local-lvm"
body := proxmox.VirtualEnvironmentContainerCreateRequestBody{
ConsoleEnabled: &consoleEnabled,
ConsoleMode: &consoleMode,
CPUArchitecture: &cpuArchitecture,
CPUCores: &cpuCores,
CPUUnits: &cpuUnits,
DatastoreID: &datastoreID,
DedicatedMemory: &memoryDedicated,
NetworkInterfaces: networkInterfaceArray,
OSTemplateFileVolume: operatingSystemTemplateFileID,
OSTemplateFileVolume: &operatingSystemTemplateFileID,
OSType: &operatingSystemType,
StartOnBoot: &started,
Swap: &memorySwap,
TTY: &consoleTTYCount,
VMID: vmID,
VMID: &vmID,
}
if description != "" {
@ -728,6 +730,13 @@ func resourceVirtualEnvironmentContainerCreate(d *schema.ResourceData, m interfa
d.SetId(strconv.Itoa(vmID))
// Wait for the container's lock to be released.
err = veClient.WaitForContainerLock(nodeName, vmID, 300, 5)
if err != nil {
return err
}
return resourceVirtualEnvironmentContainerCreateStart(d, m)
}
@ -815,10 +824,11 @@ func resourceVirtualEnvironmentContainerRead(d *schema.ResourceData, m interface
}
// Retrieve the entire configuration in order to compare it to the state.
_, err = veClient.GetContainer(nodeName, vmID)
containerConfig, err := veClient.GetContainer(nodeName, vmID)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") {
if strings.Contains(err.Error(), "HTTP 404") ||
(strings.Contains(err.Error(), "HTTP 500") && strings.Contains(err.Error(), "does not exist")) {
d.SetId("")
return nil
@ -827,6 +837,276 @@ func resourceVirtualEnvironmentContainerRead(d *schema.ResourceData, m interface
return err
}
// Compare the primitive values to those stored in the state.
if containerConfig.Description != nil {
d.Set(mkResourceVirtualEnvironmentContainerDescription, strings.TrimSpace(*containerConfig.Description))
} else {
d.Set(mkResourceVirtualEnvironmentContainerDescription, "")
}
// Compare the console configuration to the one stored in the state.
console := map[string]interface{}{}
if containerConfig.ConsoleEnabled != nil {
console[mkResourceVirtualEnvironmentContainerConsoleEnabled] = *containerConfig.ConsoleEnabled
} else {
// Default value of "console" is "1" according to the API documentation.
console[mkResourceVirtualEnvironmentContainerConsoleEnabled] = true
}
if containerConfig.ConsoleMode != nil {
console[mkResourceVirtualEnvironmentContainerConsoleMode] = *containerConfig.ConsoleMode
} else {
// Default value of "cmode" is "tty" according to the API documentation.
console[mkResourceVirtualEnvironmentContainerConsoleMode] = "tty"
}
if containerConfig.TTY != nil {
console[mkResourceVirtualEnvironmentContainerConsoleTTYCount] = *containerConfig.TTY
} else {
// Default value of "tty" is "2" according to the API documentation.
console[mkResourceVirtualEnvironmentContainerConsoleTTYCount] = 2
}
currentConsole := d.Get(mkResourceVirtualEnvironmentContainerConsole).([]interface{})
if len(currentConsole) > 0 ||
console[mkResourceVirtualEnvironmentContainerConsoleEnabled] != proxmox.CustomBool(dvResourceVirtualEnvironmentContainerConsoleEnabled) ||
console[mkResourceVirtualEnvironmentContainerConsoleMode] != dvResourceVirtualEnvironmentContainerConsoleMode ||
console[mkResourceVirtualEnvironmentContainerConsoleTTYCount] != dvResourceVirtualEnvironmentContainerConsoleTTYCount {
d.Set(mkResourceVirtualEnvironmentContainerConsole, []interface{}{console})
}
// Compare the CPU configuration to the one stored in the state.
cpu := map[string]interface{}{}
if containerConfig.CPUArchitecture != nil {
cpu[mkResourceVirtualEnvironmentContainerCPUArchitecture] = *containerConfig.CPUArchitecture
} else {
// Default value of "arch" is "amd64" according to the API documentation.
cpu[mkResourceVirtualEnvironmentContainerCPUArchitecture] = "amd64"
}
if containerConfig.CPUCores != nil {
cpu[mkResourceVirtualEnvironmentContainerCPUCores] = *containerConfig.CPUCores
} else {
// Default value of "cores" is "1" according to the API documentation.
cpu[mkResourceVirtualEnvironmentContainerCPUCores] = 1
}
if containerConfig.CPUUnits != nil {
cpu[mkResourceVirtualEnvironmentContainerCPUUnits] = *containerConfig.CPUUnits
} else {
// Default value of "cpuunits" is "1024" according to the API documentation.
cpu[mkResourceVirtualEnvironmentContainerCPUUnits] = 1024
}
currentCPU := d.Get(mkResourceVirtualEnvironmentContainerCPU).([]interface{})
if len(currentCPU) > 0 ||
cpu[mkResourceVirtualEnvironmentContainerCPUArchitecture] != dvResourceVirtualEnvironmentContainerCPUArchitecture ||
cpu[mkResourceVirtualEnvironmentContainerCPUCores] != dvResourceVirtualEnvironmentContainerCPUCores ||
cpu[mkResourceVirtualEnvironmentContainerCPUUnits] != dvResourceVirtualEnvironmentContainerCPUUnits {
d.Set(mkResourceVirtualEnvironmentContainerCPU, []interface{}{cpu})
}
// Compare the memory configuration to the one stored in the state.
memory := map[string]interface{}{}
if containerConfig.DedicatedMemory != nil {
memory[mkResourceVirtualEnvironmentContainerMemoryDedicated] = *containerConfig.DedicatedMemory
} else {
memory[mkResourceVirtualEnvironmentContainerMemoryDedicated] = 0
}
if containerConfig.Swap != nil {
memory[mkResourceVirtualEnvironmentContainerMemorySwap] = *containerConfig.Swap
} else {
memory[mkResourceVirtualEnvironmentContainerMemorySwap] = 0
}
currentMemory := d.Get(mkResourceVirtualEnvironmentContainerMemory).([]interface{})
if len(currentMemory) > 0 ||
memory[mkResourceVirtualEnvironmentContainerMemoryDedicated] != dvResourceVirtualEnvironmentContainerMemoryDedicated ||
memory[mkResourceVirtualEnvironmentContainerMemorySwap] != dvResourceVirtualEnvironmentContainerMemorySwap {
d.Set(mkResourceVirtualEnvironmentContainerMemory, []interface{}{memory})
}
// Compare the initialization and network interface configuration to the one stored in the state.
initialization := map[string]interface{}{}
if containerConfig.DNSDomain != nil || containerConfig.DNSServer != nil {
initializationDNS := map[string]interface{}{}
if containerConfig.DNSDomain != nil {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSDomain] = *containerConfig.DNSDomain
} else {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSDomain] = ""
}
if containerConfig.DNSServer != nil {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSServer] = *containerConfig.DNSServer
} else {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSServer] = ""
}
initialization[mkResourceVirtualEnvironmentContainerInitializationDNS] = []interface{}{initializationDNS}
}
if containerConfig.Hostname != nil {
initialization[mkResourceVirtualEnvironmentContainerInitializationHostname] = *containerConfig.Hostname
} else {
initialization[mkResourceVirtualEnvironmentContainerInitializationHostname] = ""
}
ipConfigList := []interface{}{}
networkInterfaceArray := []*proxmox.VirtualEnvironmentContainerCustomNetworkInterface{
containerConfig.NetworkInterface0,
containerConfig.NetworkInterface1,
containerConfig.NetworkInterface2,
containerConfig.NetworkInterface3,
containerConfig.NetworkInterface4,
containerConfig.NetworkInterface5,
containerConfig.NetworkInterface6,
containerConfig.NetworkInterface7,
}
networkInterfaceList := []interface{}{}
for _, nv := range networkInterfaceArray {
if nv == nil {
continue
}
if nv.IPv4Address != nil || nv.IPv4Gateway != nil || nv.IPv6Address != nil || nv.IPv6Gateway != nil {
ipConfig := map[string]interface{}{}
if nv.IPv4Address != nil || nv.IPv4Gateway != nil {
ip := map[string]interface{}{}
if nv.IPv4Address != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address] = *nv.IPv4Address
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address] = ""
}
if nv.IPv4Gateway != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway] = *nv.IPv4Gateway
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway] = ""
}
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4] = []interface{}{ip}
} else {
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4] = []interface{}{}
}
if nv.IPv6Address != nil || nv.IPv6Gateway != nil {
ip := map[string]interface{}{}
if nv.IPv6Address != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address] = *nv.IPv6Address
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address] = ""
}
if nv.IPv6Gateway != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway] = *nv.IPv6Gateway
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway] = ""
}
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6] = []interface{}{ip}
} else {
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6] = []interface{}{}
}
ipConfigList = append(ipConfigList, ipConfig)
}
networkInterface := map[string]interface{}{}
if nv.Bridge != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge] = *nv.Bridge
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge] = ""
}
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled] = true
if nv.MACAddress != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress] = *nv.MACAddress
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress] = ""
}
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceName] = nv.Name
if nv.RateLimit != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit] = *nv.RateLimit
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit] = 0
}
if nv.Tag != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID] = *nv.Tag
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID] = 0
}
networkInterfaceList = append(networkInterfaceList, networkInterface)
}
initialization[mkResourceVirtualEnvironmentContainerInitializationIPConfig] = ipConfigList
currentInitialization := d.Get(mkResourceVirtualEnvironmentContainerInitialization).([]interface{})
if len(currentInitialization) > 0 {
currentInitializationMap := currentInitialization[0].(map[string]interface{})
initialization[mkResourceVirtualEnvironmentContainerInitializationUserAccount] = currentInitializationMap[mkResourceVirtualEnvironmentContainerInitializationUserAccount].([]interface{})
}
if len(initialization) > 0 {
d.Set(mkResourceVirtualEnvironmentContainerInitialization, []interface{}{initialization})
} else {
d.Set(mkResourceVirtualEnvironmentContainerInitialization, []interface{}{})
}
d.Set(mkResourceVirtualEnvironmentContainerNetworkInterface, networkInterfaceList)
// Compare the operating system configuration to the one stored in the state.
operatingSystem := map[string]interface{}{}
if containerConfig.OSType != nil {
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType] = *containerConfig.OSType
} else {
// Default value of "ostype" is "" according to the API documentation.
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType] = ""
}
currentOperatingSystem := d.Get(mkResourceVirtualEnvironmentContainerOperatingSystem).([]interface{})
if len(currentOperatingSystem) > 0 {
currentOperatingSystemMap := currentOperatingSystem[0].(map[string]interface{})
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID] = currentOperatingSystemMap[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID]
}
if len(currentOperatingSystem) > 0 ||
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType] != dvResourceVirtualEnvironmentContainerOperatingSystemType {
d.Set(mkResourceVirtualEnvironmentContainerOperatingSystem, []interface{}{operatingSystem})
}
// Determine the state of the container in order to update the "started" argument.
status, err := veClient.GetContainerStatus(nodeName, vmID)
if err != nil {
return err
}
d.Set(mkResourceVirtualEnvironmentContainerStarted, status.Status == "running")
return nil
}
@ -845,13 +1125,132 @@ func resourceVirtualEnvironmentContainerUpdate(d *schema.ResourceData, m interfa
return err
}
// Retrieve the entire configuration as we need to process certain values.
_, err = veClient.GetContainer(nodeName, vmID)
// Prepare the new request object.
body := proxmox.VirtualEnvironmentContainerUpdateRequestBody{}
rebootRequired := false
resource := resourceVirtualEnvironmentContainer()
// Prepare the new primitive values.
if d.HasChange(mkResourceVirtualEnvironmentContainerDescription) {
description := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string)
body.Description = &description
}
// Prepare the new console configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerConsole) {
consoleBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentContainerConsole}, 0, true)
if err != nil {
return err
}
consoleEnabled := proxmox.CustomBool(consoleBlock[mkResourceVirtualEnvironmentContainerConsoleEnabled].(bool))
consoleMode := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleMode].(string)
consoleTTYCount := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleTTYCount].(int)
body.ConsoleEnabled = &consoleEnabled
body.ConsoleMode = &consoleMode
body.TTY = &consoleTTYCount
rebootRequired = true
}
// Prepare the new CPU configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerCPU) {
cpuBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentContainerCPU}, 0, true)
if err != nil {
return err
}
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentContainerCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentContainerCPUCores].(int)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentContainerCPUUnits].(int)
body.CPUArchitecture = &cpuArchitecture
body.CPUCores = &cpuCores
body.CPUUnits = &cpuUnits
rebootRequired = true
}
// Prepare the new memory configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerMemory) {
memoryBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentContainerMemory}, 0, true)
if err != nil {
return err
}
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentContainerMemoryDedicated].(int)
memorySwap := memoryBlock[mkResourceVirtualEnvironmentContainerMemorySwap].(int)
body.DedicatedMemory = &memoryDedicated
body.Swap = &memorySwap
rebootRequired = true
}
// Update the configuration now that everything has been prepared.
err = veClient.UpdateContainer(nodeName, vmID, &body)
if err != nil {
return err
}
// Determine if the state of the container needs to be changed.
if d.HasChange(mkResourceVirtualEnvironmentContainerStarted) {
started := d.Get(mkResourceVirtualEnvironmentContainerStarted).(bool)
if started {
err = veClient.StartContainer(nodeName, vmID)
if err != nil {
return err
}
err = veClient.WaitForContainerState(nodeName, vmID, "running", 120, 5)
if err != nil {
return err
}
} else {
forceStop := proxmox.CustomBool(true)
shutdownTimeout := 300
err = veClient.ShutdownContainer(nodeName, vmID, &proxmox.VirtualEnvironmentContainerShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
})
if err != nil {
return err
}
err = veClient.WaitForContainerState(nodeName, vmID, "stopped", 30, 5)
if err != nil {
return err
}
rebootRequired = false
}
}
// As a final step in the update procedure, we might need to reboot the virtual machine.
if rebootRequired {
rebootTimeout := 300
err = veClient.RebootContainer(nodeName, vmID, &proxmox.VirtualEnvironmentContainerRebootRequestBody{
Timeout: &rebootTimeout,
})
if err != nil {
return err
}
}
return resourceVirtualEnvironmentContainerRead(d, m)
}
@ -870,6 +1269,33 @@ func resourceVirtualEnvironmentContainerDelete(d *schema.ResourceData, m interfa
return err
}
// Shut down the container before deleting it.
status, err := veClient.GetContainerStatus(nodeName, vmID)
if err != nil {
return err
}
if status.Status != "stopped" {
forceStop := proxmox.CustomBool(true)
shutdownTimeout := 300
err = veClient.ShutdownContainer(nodeName, vmID, &proxmox.VirtualEnvironmentContainerShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
})
if err != nil {
return err
}
err = veClient.WaitForContainerState(nodeName, vmID, "stopped", 30, 5)
if err != nil {
return err
}
}
err = veClient.DeleteContainer(nodeName, vmID)
if err != nil {
@ -882,6 +1308,13 @@ func resourceVirtualEnvironmentContainerDelete(d *schema.ResourceData, m interfa
return err
}
// Wait for the state to become unavailable as that clearly indicates the destruction of the container.
err = veClient.WaitForContainerState(nodeName, vmID, "", 60, 2)
if err == nil {
return fmt.Errorf("Failed to delete container \"%d\"", vmID)
}
d.SetId("")
return nil

View File

@ -0,0 +1,237 @@
/* 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 proxmoxtf
import (
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
// TestResourceVirtualEnvironmentContainerInstantiation tests whether the ResourceVirtualEnvironmentContainer instance can be instantiated.
func TestResourceVirtualEnvironmentContainerInstantiation(t *testing.T) {
s := resourceVirtualEnvironmentContainer()
if s == nil {
t.Fatalf("Cannot instantiate resourceVirtualEnvironmentContainer")
}
}
// TestResourceVirtualEnvironmentContainerSchema tests the resourceVirtualEnvironmentContainer schema.
func TestResourceVirtualEnvironmentContainerSchema(t *testing.T) {
s := resourceVirtualEnvironmentContainer()
testRequiredArguments(t, s, []string{
mkResourceVirtualEnvironmentContainerNodeName,
mkResourceVirtualEnvironmentContainerOperatingSystem,
})
testOptionalArguments(t, s, []string{
mkResourceVirtualEnvironmentContainerCPU,
mkResourceVirtualEnvironmentContainerDescription,
mkResourceVirtualEnvironmentContainerInitialization,
mkResourceVirtualEnvironmentContainerMemory,
mkResourceVirtualEnvironmentContainerPoolID,
mkResourceVirtualEnvironmentContainerStarted,
mkResourceVirtualEnvironmentContainerVMID,
})
testSchemaValueTypes(t, s, []string{
mkResourceVirtualEnvironmentContainerCPU,
mkResourceVirtualEnvironmentContainerDescription,
mkResourceVirtualEnvironmentContainerInitialization,
mkResourceVirtualEnvironmentContainerMemory,
mkResourceVirtualEnvironmentContainerOperatingSystem,
mkResourceVirtualEnvironmentContainerPoolID,
mkResourceVirtualEnvironmentContainerStarted,
mkResourceVirtualEnvironmentContainerVMID,
}, []schema.ValueType{
schema.TypeList,
schema.TypeString,
schema.TypeList,
schema.TypeList,
schema.TypeList,
schema.TypeString,
schema.TypeBool,
schema.TypeInt,
})
cpuSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentContainerCPU)
testOptionalArguments(t, cpuSchema, []string{
mkResourceVirtualEnvironmentContainerCPUArchitecture,
mkResourceVirtualEnvironmentContainerCPUCores,
mkResourceVirtualEnvironmentContainerCPUUnits,
})
testSchemaValueTypes(t, cpuSchema, []string{
mkResourceVirtualEnvironmentContainerCPUArchitecture,
mkResourceVirtualEnvironmentContainerCPUCores,
mkResourceVirtualEnvironmentContainerCPUUnits,
}, []schema.ValueType{
schema.TypeString,
schema.TypeInt,
schema.TypeInt,
})
initializationSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentContainerInitialization)
testOptionalArguments(t, initializationSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationDNS,
mkResourceVirtualEnvironmentContainerInitializationHostname,
mkResourceVirtualEnvironmentContainerInitializationIPConfig,
mkResourceVirtualEnvironmentContainerInitializationUserAccount,
})
testSchemaValueTypes(t, initializationSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationDNS,
mkResourceVirtualEnvironmentContainerInitializationHostname,
mkResourceVirtualEnvironmentContainerInitializationIPConfig,
mkResourceVirtualEnvironmentContainerInitializationUserAccount,
}, []schema.ValueType{
schema.TypeList,
schema.TypeString,
schema.TypeList,
schema.TypeList,
})
initializationDNSSchema := testNestedSchemaExistence(t, initializationSchema, mkResourceVirtualEnvironmentContainerInitializationDNS)
testOptionalArguments(t, initializationDNSSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationDNSDomain,
mkResourceVirtualEnvironmentContainerInitializationDNSServer,
})
testSchemaValueTypes(t, initializationDNSSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationDNSDomain,
mkResourceVirtualEnvironmentContainerInitializationDNSServer,
}, []schema.ValueType{
schema.TypeString,
schema.TypeString,
})
initializationIPConfigSchema := testNestedSchemaExistence(t, initializationSchema, mkResourceVirtualEnvironmentContainerInitializationIPConfig)
testOptionalArguments(t, initializationIPConfigSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4,
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6,
})
testSchemaValueTypes(t, initializationIPConfigSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4,
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6,
}, []schema.ValueType{
schema.TypeList,
schema.TypeList,
})
initializationIPConfigIPv4Schema := testNestedSchemaExistence(t, initializationIPConfigSchema, mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4)
testOptionalArguments(t, initializationIPConfigIPv4Schema, []string{
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address,
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway,
})
testSchemaValueTypes(t, initializationIPConfigIPv4Schema, []string{
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address,
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway,
}, []schema.ValueType{
schema.TypeString,
schema.TypeString,
})
initializationIPConfigIPv6Schema := testNestedSchemaExistence(t, initializationIPConfigSchema, mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6)
testOptionalArguments(t, initializationIPConfigIPv6Schema, []string{
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address,
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway,
})
testSchemaValueTypes(t, initializationIPConfigIPv6Schema, []string{
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address,
mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway,
}, []schema.ValueType{
schema.TypeString,
schema.TypeString,
})
initializationUserAccountSchema := testNestedSchemaExistence(t, initializationSchema, mkResourceVirtualEnvironmentContainerInitializationUserAccount)
testOptionalArguments(t, initializationUserAccountSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationUserAccountKeys,
mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword,
})
testSchemaValueTypes(t, initializationUserAccountSchema, []string{
mkResourceVirtualEnvironmentContainerInitializationUserAccountKeys,
mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword,
}, []schema.ValueType{
schema.TypeList,
schema.TypeString,
})
memorySchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentContainerMemory)
testOptionalArguments(t, memorySchema, []string{
mkResourceVirtualEnvironmentContainerMemoryDedicated,
mkResourceVirtualEnvironmentContainerMemorySwap,
})
testSchemaValueTypes(t, memorySchema, []string{
mkResourceVirtualEnvironmentContainerMemoryDedicated,
mkResourceVirtualEnvironmentContainerMemorySwap,
}, []schema.ValueType{
schema.TypeInt,
schema.TypeInt,
})
networkInterfaceSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentContainerNetworkInterface)
testRequiredArguments(t, networkInterfaceSchema, []string{
mkResourceVirtualEnvironmentContainerNetworkInterfaceName,
})
testOptionalArguments(t, networkInterfaceSchema, []string{
mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge,
mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled,
mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress,
mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit,
mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID,
})
testSchemaValueTypes(t, networkInterfaceSchema, []string{
mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge,
mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled,
mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress,
mkResourceVirtualEnvironmentContainerNetworkInterfaceName,
mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit,
mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID,
}, []schema.ValueType{
schema.TypeString,
schema.TypeBool,
schema.TypeString,
schema.TypeString,
schema.TypeFloat,
schema.TypeInt,
})
operatingSystemSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentContainerOperatingSystem)
testRequiredArguments(t, operatingSystemSchema, []string{
mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID,
})
testOptionalArguments(t, operatingSystemSchema, []string{
mkResourceVirtualEnvironmentContainerOperatingSystemType,
})
testSchemaValueTypes(t, operatingSystemSchema, []string{
mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID,
mkResourceVirtualEnvironmentContainerOperatingSystemType,
}, []schema.ValueType{
schema.TypeString,
schema.TypeString,
})
}

View File

@ -402,7 +402,8 @@ func resourceVirtualEnvironmentFileGetContentType(d *schema.ResourceData, m inte
}
if contentType == "" {
if strings.HasSuffix(sourceFilePath, ".tar.xz") {
if strings.HasSuffix(sourceFilePath, ".tar.gz") ||
strings.HasSuffix(sourceFilePath, ".tar.xz") {
contentType = "vztmpl"
} else {
ext := strings.TrimLeft(strings.ToLower(filepath.Ext(sourceFilePath)), ".")

View File

@ -547,7 +547,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
Sensitive: true,
Default: dvResourceVirtualEnvironmentVMInitializationUserAccountPassword,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return strings.ReplaceAll(old, "*", "") == ""
return len(old) > 0 && strings.ReplaceAll(old, "*", "") == ""
},
},
mkResourceVirtualEnvironmentVMInitializationUserAccountUsername: {
@ -1484,7 +1484,8 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err
vmConfig, err := veClient.GetVM(nodeName, vmID)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") {
if strings.Contains(err.Error(), "HTTP 404") ||
(strings.Contains(err.Error(), "HTTP 500") && strings.Contains(err.Error(), "does not exist")) {
d.SetId("")
return nil
@ -2502,28 +2503,37 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e
}
// Shut down the virtual machine before deleting it.
forceStop := proxmox.CustomBool(true)
shutdownTimeout := 300
err = veClient.ShutdownVM(nodeName, vmID, &proxmox.VirtualEnvironmentVMShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
})
status, err := veClient.GetVMStatus(nodeName, vmID)
if err != nil {
return err
}
err = veClient.WaitForVMState(nodeName, vmID, "stopped", 30, 5)
if status.Status != "stopped" {
forceStop := proxmox.CustomBool(true)
shutdownTimeout := 300
if err != nil {
return err
err = veClient.ShutdownVM(nodeName, vmID, &proxmox.VirtualEnvironmentVMShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
})
if err != nil {
return err
}
err = veClient.WaitForVMState(nodeName, vmID, "stopped", 30, 5)
if err != nil {
return err
}
}
err = veClient.DeleteVM(nodeName, vmID)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") {
if strings.Contains(err.Error(), "HTTP 404") ||
(strings.Contains(err.Error(), "HTTP 500") && strings.Contains(err.Error(), "does not exist")) {
d.SetId("")
return nil
@ -2533,7 +2543,7 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e
}
// Wait for the state to become unavailable as that clearly indicates the destruction of the VM.
err = veClient.WaitForVMState(nodeName, vmID, "", 30, 2)
err = veClient.WaitForVMState(nodeName, vmID, "", 60, 2)
if err == nil {
return fmt.Errorf("Failed to delete VM \"%d\"", vmID)