diff --git a/CHANGELOG.md b/CHANGELOG.md index f3380470..aa2e00f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ ## 0.2.0 (UNRELEASED) +BREAKING CHANGES: + +* resource/virtual_environment_vm: Rename `cloud_init` argument to `initialization` +* resource/virtual_environment_vm: Rename `os_type` argument to `os.type` + FEATURES: * **New Data Source:** `proxmox_virtual_environment_dns` * **New Data Source:** `proxmox_virtual_environment_hosts` * **New Resource:** `proxmox_virtual_environment_certificate` +* **New Resource:** `proxmox_virtual_environment_container` * **New Resource:** `proxmox_virtual_environment_dns` * **New Resource:** `proxmox_virtual_environment_hosts` @@ -12,7 +18,7 @@ ENHANCEMENTS: * resource/virtual_environment_vm: Add `acpi` argument * resource/virtual_environment_vm: Add `bios` argument -* resource/virtual_environment_vm: Add `cpu.flags`, `cpu.type` and `cpu.units` arguments +* resource/virtual_environment_vm: Add `cpu.architecture`, `cpu.flags`, `cpu.type` and `cpu.units` arguments * resource/virtual_environment_vm: Add `tablet_device` argument * resource/virtual_environment_vm: Add `vga` argument diff --git a/README.md b/README.md index ea128146..c17aa457 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ A Terraform Provider which adds support for Proxmox solutions. - [Resources](#resources) - [Virtual Environment](#virtual-environment-1) - [Certificate](#certificate-proxmox_virtual_environment_certificate) + - [Container](#container-proxmox_virtual_environment_container) - [DNS](#dns-proxmox_virtual_environment_dns-1) - [File](#file-proxmox_virtual_environment_file) - [Group](#group-proxmox_virtual_environment_group-1) @@ -38,7 +39,7 @@ A Terraform Provider which adds support for Proxmox solutions. - [Pool](#pool-proxmox_virtual_environment_pool-1) - [Role](#role-proxmox_virtual_environment_role-1) - [User](#user-proxmox_virtual_environment_user-1) - - [VM](#vm-proxmox_virtual_environment_vm) + - [Virtual Machine](#virtual-machine-proxmox_virtual_environment_vm) - [Developing the Provider](#developing-the-provider) - [Testing the Provider](#testing-the-provider) - [Known issues](#known-issues) @@ -284,6 +285,70 @@ This data source doesn't accept arguments. * `subject` - The subject * `subject_alternative_names` - The subject alternative names +##### Container (proxmox_virtual_environment_container) + +###### Arguments +* `console` - (Optional) The console configuration + * `enabled` - (Optional) Whether to enable the console device (defaults to `true`) + * `mode` - (Optional) The console mode (defaults to `tty`) + * `console` - Console + * `shell` - Shell + * `tty` - TTY + * `tty_count` - (Optional) The number of available TTY (defaults to `2`) +* `cpu` - (Optional) The CPU configuration + * `architecture` - (Optional) The CPU architecture (defaults to `amd64`) + * `amd64` - x86 (64 bit) + * `arm64` - ARM (64-bit) + * `armhf` - ARM (32 bit) + * `i386` - x86 (32 bit) + * `cores` - (Optional) The number of CPU cores (defaults to `1`) + * `units` - (Optional) The CPU units (defaults to `1024`) +* `description` - (Optional) The description +* `initialization` - (Optional) The initialization configuration + * `dns` - (Optional) The DNS configuration + * `domain` - (Optional) The DNS search domain + * `server` - (Optional) The DNS server + * `hostname` - (Optional) The hostname + * `ip_config` - (Optional) The IP configuration (one block per network device) + * `ipv4` - (Optional) The IPv4 configuration + * `address` - (Optional) The IPv4 address (use `dhcp` for autodiscovery) + * `gateway` - (Optional) The IPv4 gateway (must be omitted when `dhcp` is used as the address) + * `ipv6` - (Optional) The IPv4 configuration + * `address` - (Optional) The IPv6 address (use `dhcp` for autodiscovery) + * `gateway` - (Optional) The IPv6 gateway (must be omitted when `dhcp` is used as the address) + * `user_account` - (Optional) The user account configuration + * `keys` - (Optional) The SSH keys for the root account + * `password` - (Optional) The password for the root account +* `memory` - (Optional) The memory configuration + * `dedicated` - (Optional) The dedicated memory in megabytes (defaults to `512`) + * `swap` - (Optional) The swap size in megabytes (defaults to `0`) +* `network_interface` - (Optional) A network interface (multiple blocks supported) + * `bridge` - (Optional) The name of the network bridge (defaults to `vmbr0`) + * `enabled` - (Optional) Whether to enable the network device (defaults to `true`) + * `mac_address` - (Optional) The MAC address + * `name` - (Required) The network interface name + * `rate_limit` - (Optional) The rate limit in megabytes per second + * `vlan_id` - (Optional) The VLAN identifier +* `node_name` - (Required) The name of the node to assign the container to +* `operating_system` - (Required) The Operating System configuration + * `template_file_id` - (Required) The ID of an OS template file + * `type` - (Optional) The type (defaults to `unmanaged`) + * `alpine` - Alpine + * `archlinux` - Arch Linux + * `centos` - CentOS + * `debian` - Debian + * `fedora` - Fedora + * `gentoo` - Gentoo + * `opensuse` - openSUSE + * `ubuntu` - Ubuntu + * `unmanaged` - Unmanaged +* `pool_id` - (Optional) The ID of a pool to assign the container to +* `started` - (Optional) Whether to start the container (defaults to `true`) +* `vm_id` - (Optional) The ID + +###### Attributes +This resource doesn't expose any additional attributes. + ##### DNS (proxmox_virtual_environment_dns) ###### Arguments @@ -400,7 +465,7 @@ This resource doesn't expose any additional attributes. ###### Attributes This resource doesn't expose any additional attributes. -##### VM (proxmox_virtual_environment_vm) +##### Virtual Machine (proxmox_virtual_environment_vm) ###### Arguments * `acpi` - (Optional) Whether to enable ACPI (defaults to `true`) @@ -416,23 +481,10 @@ This resource doesn't expose any additional attributes. * `cdrom` - (Optional) The CDROM configuration * `enabled` - (Optional) Whether to enable the CDROM drive (defaults to `false`) * `file_id` - (Optional) A file ID for an ISO file (defaults to `cdrom` as in the physical drive) -* `cloud_init` - (Optional) The cloud-init configuration (conflicts with `cdrom`) - * `dns` - (Optional) The DNS configuration - * `domain` - (Optional) The DNS search domain - * `server` - (Optional) The DNS server - * `ip_config` - (Optional) The IP configuration (one block per network device) - * `ipv4` - (Optional) The IPv4 configuration - * `address` - (Optional) The IPv4 address (use `dhcp` for autodiscovery) - * `gateway` - (Optional) The IPv4 gateway (must be omitted when `dhcp` is used as the address) - * `ipv6` - (Optional) The IPv4 configuration - * `address` - (Optional) The IPv6 address (use `dhcp` for autodiscovery) - * `gateway` - (Optional) The IPv6 gateway (must be omitted when `dhcp` is used as the address) - * `user_account` - (Required) The user account configuration (conflicts with `user_data_file_id`) - * `keys` - (Required) The SSH keys - * `password` - (Optional) The SSH password - * `username` - (Required) The SSH username - * `user_data_file_id` - (Optional) The ID of a file containing custom user data (conflicts with `user_account`) * `cpu` - (Optional) The CPU configuration + * `architecture` - (Optional) The CPU architecture (defaults to `x86_64`) + * `aarch64` - ARM (64 bit) + * `x86_64` - x86 (64-bit) * `cores` - (Optional) The number of CPU cores (defaults to `1`) * `flags` - (Optional) The CPU flags * `+aes`/`-aes` - Activate AES instruction set for HW acceleration @@ -482,7 +534,7 @@ This resource doesn't expose any additional attributes. * `qemu32`/`qemu64` - QEMU Virtual CPU version 2.5+ (32 & 64 bit variants) * `units` - (Optional) The CPU units (defaults to `1024`) * `description` - (Optional) The description -* `disk` - (Optional) The disk configuration (multiple blocks supported) +* `disk` - (Optional) A disk (multiple blocks supported) * `datastore_id` - (Optional) The ID of the datastore to create the disk in (defaults to `local-lvm`) * `file_format` - (Optional) The file format (defaults to `qcow2`) * `qcow2` - QEMU Disk Image v2 @@ -495,6 +547,22 @@ This resource doesn't expose any additional attributes. * `read_burstable` - (Optional) The maximum burstable read speed in megabytes per second * `write` - (Optional) The maximum write speed in megabytes per second * `write_burstable` - (Optional) The maximum burstable write speed in megabytes per second +* `initialization` - (Optional) The cloud-init configuration (conflicts with `cdrom`) + * `dns` - (Optional) The DNS configuration + * `domain` - (Optional) The DNS search domain + * `server` - (Optional) The DNS server + * `ip_config` - (Optional) The IP configuration (one block per network device) + * `ipv4` - (Optional) The IPv4 configuration + * `address` - (Optional) The IPv4 address (use `dhcp` for autodiscovery) + * `gateway` - (Optional) The IPv4 gateway (must be omitted when `dhcp` is used as the address) + * `ipv6` - (Optional) The IPv4 configuration + * `address` - (Optional) The IPv6 address (use `dhcp` for autodiscovery) + * `gateway` - (Optional) The IPv6 gateway (must be omitted when `dhcp` is used as the address) + * `user_account` - (Optional) The user account configuration (conflicts with `user_data_file_id`) + * `keys` - (Optional) The SSH keys + * `password` - (Optional) The SSH password + * `username` - (Optional) The SSH username + * `user_data_file_id` - (Optional) The ID of a file containing custom user data (conflicts with `user_account`) * `keyboard_layout` - (Optional) The keyboard layout (defaults to `en-us`) * `da` - Danish * `de` - German @@ -526,7 +594,7 @@ This resource doesn't expose any additional attributes. * `floating` - (Optional) The floating memory in megabytes (defaults to `0`) * `shared` - (Optional) The shared memory in megabytes (defaults to `0`) * `name` - (Optional) The name -* `network_device` - (Optional) The network device configuration (multiple blocks supported) +* `network_device` - (Optional) A network device (multiple blocks supported) * `bridge` - (Optional) The name of the network bridge (defaults to `vmbr0`) * `enabled` - (Optional) Whether to enable the network device (defaults to `true`) * `mac_address` - (Optional) The MAC address @@ -538,19 +606,20 @@ This resource doesn't expose any additional attributes. * `rate_limit` - (Optional) The rate limit in megabytes per second * `vlan_id` - (Optional) The VLAN identifier * `node_name` - (Required) The name of the node to assign the virtual machine to -* `os_type` - (Optional) The OS type (defaults to `other`) - * `l24` - Linux Kernel 2.4 - * `l26` - Linux Kernel 2.6 - 5.X - * `other` - Unspecified OS - * `solaris` - OpenIndiania, OpenSolaris og Solaris Kernel - * `w2k` - Windows 2000 - * `w2k3` - Windows 2003 - * `w2k8` - Windows 2008 - * `win7` - Windows 7 - * `win8` - Windows 8, 2012 or 2012 R2 - * `win10` - Windows 10 or 2016 - * `wvista` - Windows Vista - * `wxp` - Windows XP +* `operating_system` - (Required) The Operating System configuration + * `type` - (Optional) The type (defaults to `other`) + * `l24` - Linux Kernel 2.4 + * `l26` - Linux Kernel 2.6 - 5.X + * `other` - Unspecified OS + * `solaris` - OpenIndiania, OpenSolaris og Solaris Kernel + * `w2k` - Windows 2000 + * `w2k3` - Windows 2003 + * `w2k8` - Windows 2008 + * `win7` - Windows 7 + * `win8` - Windows 8, 2012 or 2012 R2 + * `win10` - Windows 10 or 2016 + * `wvista` - Windows Vista + * `wxp` - Windows XP * `pool_id` - (Optional) The ID of a pool to assign the virtual machine to * `started` - (Optional) Whether to start the virtual machine (defaults to `true`) * `tablet_device` - (Optional) Whether to enable the USB tablet device (defaults to `true`) diff --git a/example/resource_virtual_environment_vm.tf b/example/resource_virtual_environment_vm.tf index 1f374c95..e79f7402 100644 --- a/example/resource_virtual_environment_vm.tf +++ b/example/resource_virtual_environment_vm.tf @@ -3,7 +3,14 @@ resource "proxmox_virtual_environment_vm" "example" { enabled = true } - cloud_init { + description = "Managed by Terraform" + + disk { + datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local-lvm"))}" + file_id = "${proxmox_virtual_environment_file.ubuntu_cloud_image.id}" + } + + initialization { dns { server = "1.1.1.1" } @@ -23,21 +30,18 @@ resource "proxmox_virtual_environment_vm" "example" { user_data_file_id = "${proxmox_virtual_environment_file.cloud_config.id}" } - description = "Managed by Terraform" - - disk { - datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local-lvm"))}" - file_id = "${proxmox_virtual_environment_file.ubuntu_cloud_image.id}" - } - name = "terraform-provider-proxmox-example" network_device {} node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}" - os_type = "l26" - pool_id = "${proxmox_virtual_environment_pool.example.id}" - vm_id = 2038 + + operating_system { + type = "l26" + } + + pool_id = "${proxmox_virtual_environment_pool.example.id}" + vm_id = 2038 connection { type = "ssh" diff --git a/proxmox/virtual_environment_container.go b/proxmox/virtual_environment_container.go new file mode 100644 index 00000000..c9bfaab4 --- /dev/null +++ b/proxmox/virtual_environment_container.go @@ -0,0 +1,112 @@ +/* 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 proxmox + +import ( + "errors" + "fmt" + "net/url" + "strings" + "time" +) + +// CreateContainer creates a container. +func (c *VirtualEnvironmentClient) CreateContainer(nodeName string, d *VirtualEnvironmentContainerCreateRequestBody) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/lxc", url.PathEscape(nodeName)), d, nil) +} + +// DeleteContainer deletes a container. +func (c *VirtualEnvironmentClient) DeleteContainer(nodeName string, vmID int) error { + return c.DoRequest(hmDELETE, fmt.Sprintf("nodes/%s/lxc/%d", url.PathEscape(nodeName), vmID), nil, nil) +} + +// GetContainer retrieves a container. +func (c *VirtualEnvironmentClient) GetContainer(nodeName string, vmID int) (*VirtualEnvironmentContainerGetResponseData, error) { + resBody := &VirtualEnvironmentContainerGetResponseBody{} + err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/lxc/%d/config", url.PathEscape(nodeName), vmID), nil, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + return resBody.Data, nil +} + +// GetContainerStatus retrieves the status for a container. +func (c *VirtualEnvironmentClient) GetContainerStatus(nodeName string, vmID int) (*VirtualEnvironmentContainerGetStatusResponseData, error) { + resBody := &VirtualEnvironmentContainerGetStatusResponseBody{} + err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/lxc/%d/status/current", url.PathEscape(nodeName), vmID), nil, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + return resBody.Data, nil +} + +// RebootContainer reboots a container. +func (c *VirtualEnvironmentClient) RebootContainer(nodeName string, vmID int, d *VirtualEnvironmentContainerRebootRequestBody) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/lxc/%d/status/reboot", url.PathEscape(nodeName), vmID), d, nil) +} + +// ShutdownContainer shuts down a container. +func (c *VirtualEnvironmentClient) ShutdownContainer(nodeName string, vmID int, d *VirtualEnvironmentContainerShutdownRequestBody) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/lxc/%d/status/shutdown", url.PathEscape(nodeName), vmID), d, nil) +} + +// StartContainer starts a container. +func (c *VirtualEnvironmentClient) StartContainer(nodeName string, vmID int) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/lxc/%d/status/start", url.PathEscape(nodeName), vmID), nil, nil) +} + +// StopContainer stops a container immediately. +func (c *VirtualEnvironmentClient) StopContainer(nodeName string, vmID int) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/lxc/%d/status/stop", url.PathEscape(nodeName), vmID), nil, nil) +} + +// UpdateContainer updates a container. +func (c *VirtualEnvironmentClient) UpdateContainer(nodeName string, vmID int, d *VirtualEnvironmentContainerUpdateRequestBody) error { + return c.DoRequest(hmPUT, fmt.Sprintf("nodes/%s/lxc/%d/config", url.PathEscape(nodeName), vmID), d, nil) +} + +// WaitForContainerState waits for a container to reach a specific state. +func (c *VirtualEnvironmentClient) WaitForContainerState(nodeName string, vmID int, state string, timeout int, delay int) error { + state = strings.ToLower(state) + + 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.Status == state { + 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 enter the state \"%s\"", vmID, state) +} diff --git a/proxmox/virtual_environment_container_types.go b/proxmox/virtual_environment_container_types.go new file mode 100644 index 00000000..5fe09a4a --- /dev/null +++ b/proxmox/virtual_environment_container_types.go @@ -0,0 +1,777 @@ +/* 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 proxmox + +import ( + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" +) + +// VirtualEnvironmentContainerCreateRequestBody contains the data for an user create request. +type VirtualEnvironmentContainerCreateRequestBody struct { + BandwidthLimit *float64 `json:"bwlimit,omitempty" url:"bwlimit,omitempty"` + ConsoleEnabled *CustomBool `json:"console,omitempty" url:"console,omitempty,int"` + ConsoleMode *string `json:"cmode,omitempty" url:"cmode,omitempty"` + CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"` + CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"` + CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"` + CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"` + DatastoreID *string `json:"storage,omitempty" url:"storage,omitempty"` + DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"` + Description *string `json:"description,omitempty" url:"description,omitempty"` + DNSDomain *string `json:"searchdomain,omitempty" url:"searchdomain,omitempty"` + DNSServer *string `json:"nameserver,omitempty" url:"nameserver,omitempty"` + Features *VirtualEnvironmentContainerCustomFeatures `json:"features,omitempty" url:"features,omitempty"` + Force *CustomBool `json:"force,omitempty" url:"force,omitempty,int"` + HookScript *string `json:"hookscript,omitempty" url:"hookscript,omitempty"` + Hostname *string `json:"hostname,omitempty" url:"hostname,omitempty"` + IgnoreUnpackErrors *CustomBool `json:"ignore-unpack-errors,omitempty" url:"force,omitempty,int"` + 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"` + OSType *string `json:"ostype,omitempty" url:"ostype,omitempty"` + Password *string `json:"password,omitempty" url:"password,omitempty"` + PoolID *string `json:"pool,omitempty" url:"pool,omitempty"` + Protection *CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"` + Restore *CustomBool `json:"restore,omitempty" url:"restore,omitempty,int"` + RootFS *VirtualEnvironmentContainerCustomRootFS `json:"rootfs,omitempty" url:"rootfs,omitempty"` + SSHKeys *VirtualEnvironmentContainerCustomSSHKeys `json:"ssh-public-keys,omitempty" url:"ssh-public-keys,omitempty"` + Start *CustomBool `json:"start,omitempty" url:"start,omitempty,int"` + StartOnBoot *CustomBool `json:"onboot,omitempty" url:"onboot,omitempty,int"` + StartupBehavior *VirtualEnvironmentContainerCustomStartupBehavior `json:"startup,omitempty" url:"startup,omitempty"` + Swap *int `json:"swap,omitempty" url:"swap,omitempty"` + Tags *string `json:"tags,omitempty" url:"tags,omitempty"` + Template *CustomBool `json:"template,omitempty" url:"template,omitempty,int"` + 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"` +} + +// VirtualEnvironmentContainerCustomFeatures contains the values for the "features" property. +type VirtualEnvironmentContainerCustomFeatures struct { + FUSE *CustomBool `json:"fuse,omitempty" url:"fuse,omitempty,int"` + KeyControl *CustomBool `json:"keyctl,omitempty" url:"keyctl,omitempty,int"` + MountTypes *[]string `json:"mount,omitempty" url:"mount,omitempty"` + Nesting *CustomBool `json:"nesting,omitempty" url:"nesting,omitempty,int"` +} + +// VirtualEnvironmentContainerCustomMountPoint contains the values for the "mp[n]" properties. +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"` + Enabled bool `json:"-" url:"-"` + MountOptions *[]string `json:"mountoptions,omitempty" url:"mountoptions,omitempty"` + MountPoint string `json:"mp" url:"mp"` + Quota *CustomBool `json:"quota,omitempty" url:"quota,omitempty,int"` + ReadOnly *CustomBool `json:"ro,omitempty" url:"ro,omitempty,int"` + Replicate *CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"` + Shared *CustomBool `json:"shared,omitempty" url:"shared,omitempty,int"` + Volume string `json:"volume" url:"volume"` +} + +// VirtualEnvironmentContainerCustomMountPointArray is an array of VirtualEnvironmentContainerCustomMountPoint. +type VirtualEnvironmentContainerCustomMountPointArray []VirtualEnvironmentContainerCustomMountPoint + +// VirtualEnvironmentContainerCustomNetworkInterface contains the values for the "net[n]" properties. +type VirtualEnvironmentContainerCustomNetworkInterface struct { + Bridge *string `json:"bridge,omitempty" url:"bridge,omitempty"` + Enabled bool `json:"-" url:"-"` + Firewall *CustomBool `json:"firewall,omitempty" url:"firewall,omitempty,int"` + IPv4Address *string `json:"ip,omitempty" url:"ip,omitempty"` + IPv4Gateway *string `json:"gw,omitempty" url:"gw,omitempty"` + IPv6Address *string `json:"ip6,omitempty" url:"ip6,omitempty"` + IPv6Gateway *string `json:"gw6,omitempty" url:"gw6,omitempty"` + 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"` + Tag *int `json:"tag,omitempty" url:"tag,omitempty"` + Trunks *[]int `json:"trunks,omitempty" url:"trunks,omitempty"` + Type *string `json:"type,omitempty" url:"type,omitempty"` +} + +// VirtualEnvironmentContainerCustomNetworkInterfaceArray is an array of VirtualEnvironmentContainerCustomNetworkInterface. +type VirtualEnvironmentContainerCustomNetworkInterfaceArray []VirtualEnvironmentContainerCustomNetworkInterface + +// 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"` + 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"` + Replicate *CustomBool `json:"replicate,omitempty" url:"replicate,omitempty,int"` + Shared *CustomBool `json:"shared,omitempty" url:"shared,omitempty,int"` + Volume string `json:"volume" url:"volume"` +} + +// VirtualEnvironmentContainerCustomSSHKeys contains the values for the "ssh-public-keys" property. +type VirtualEnvironmentContainerCustomSSHKeys []string + +// VirtualEnvironmentContainerCustomStartupBehavior contains the values for the "startup" property. +type VirtualEnvironmentContainerCustomStartupBehavior struct { + Down *int `json:"down,omitempty" url:"down,omitempty"` + Order *int `json:"order,omitempty" url:"order,omitempty"` + Up *int `json:"up,omitempty" url:"up,omitempty"` +} + +// VirtualEnvironmentContainerGetResponseBody contains the body from an user get response. +type VirtualEnvironmentContainerGetResponseBody struct { + Data *VirtualEnvironmentContainerGetResponseData `json:"data,omitempty"` +} + +// VirtualEnvironmentContainerGetResponseData contains the data from an user get response. +type VirtualEnvironmentContainerGetResponseData struct { + ConsoleEnabled *CustomBool `json:"console,omitempty"` + ConsoleMode *string `json:"cmode,omitempty"` + CPUArchitecture *string `json:"arch,omitempty"` + CPUCores *int `json:"cores,omitempty"` + CPULimit *int `json:"cpulimit,omitempty"` + CPUUnits *int `json:"cpuunits,omitempty"` + DedicatedMemory *int `json:"memory,omitempty"` + Description *string `json:"description,omitempty"` + Digest string `json:"digest"` + DNSDomain *string `json:"searchdomain,omitempty"` + DNSServer *string `json:"nameserver,omitempty"` + Features *VirtualEnvironmentContainerCustomFeatures `json:"features,omitempty"` + HookScript *string `json:"hookscript,omitempty"` + Hostname *string `json:"hostname,omitempty"` + Lock *CustomBool `json:"lock,omitempty"` + LXCConfiguration *[]string `json:"lxc,omitempty"` + MountPoint0 VirtualEnvironmentContainerCustomMountPointArray `json:"mp0,omitempty"` + MountPoint1 VirtualEnvironmentContainerCustomMountPointArray `json:"mp1,omitempty"` + MountPoint2 VirtualEnvironmentContainerCustomMountPointArray `json:"mp2,omitempty"` + MountPoint3 VirtualEnvironmentContainerCustomMountPointArray `json:"mp3,omitempty"` + NetworkInterface0 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net0,omitempty"` + NetworkInterface1 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net1,omitempty"` + NetworkInterface2 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net2,omitempty"` + NetworkInterface3 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net3,omitempty"` + NetworkInterface4 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net4,omitempty"` + NetworkInterface5 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net5,omitempty"` + NetworkInterface6 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net6,omitempty"` + NetworkInterface7 *VirtualEnvironmentContainerCustomNetworkInterface `json:"net7,omitempty"` + OSType *string `json:"ostype,omitempty"` + Protection *CustomBool `json:"protection,omitempty"` + RootFS *VirtualEnvironmentContainerCustomRootFS `json:"rootfs,omitempty"` + StartOnBoot *CustomBool `json:"onboot,omitempty"` + StartupBehavior *VirtualEnvironmentContainerCustomStartupBehavior `json:"startup,omitempty"` + Swap *int `json:"swap,omitempty"` + Tags *string `json:"tags,omitempty"` + Template *CustomBool `json:"template,omitempty"` + TTY *int `json:"tty,omitempty"` + Unprivileged *CustomBool `json:"unprivileged,omitempty"` +} + +// VirtualEnvironmentContainerGetStatusResponseBody contains the body from a container get status response. +type VirtualEnvironmentContainerGetStatusResponseBody struct { + Data *VirtualEnvironmentContainerGetStatusResponseData `json:"data,omitempty"` +} + +// 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"` +} + +// VirtualEnvironmentContainerRebootRequestBody contains the body for a container reboot request. +type VirtualEnvironmentContainerRebootRequestBody struct { + Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"` +} + +// VirtualEnvironmentContainerShutdownRequestBody contains the body for a container shutdown request. +type VirtualEnvironmentContainerShutdownRequestBody struct { + ForceStop *CustomBool `json:"forceStop,omitempty,int" url:"forceStop,omitempty,int"` + Timeout *int `json:"timeout,omitempty" url:"timeout,omitempty"` +} + +// VirtualEnvironmentContainerUpdateRequestBody contains the data for an user update request. +type VirtualEnvironmentContainerUpdateRequestBody VirtualEnvironmentContainerCreateRequestBody + +// EncodeValues converts a VirtualEnvironmentContainerCustomFeatures struct to a URL vlaue. +func (r VirtualEnvironmentContainerCustomFeatures) EncodeValues(key string, v *url.Values) error { + values := []string{} + + if r.FUSE != nil { + if *r.FUSE { + values = append(values, "fuse=1") + } else { + values = append(values, "fuse=0") + } + } + + if r.KeyControl != nil { + if *r.KeyControl { + values = append(values, "keyctl=1") + } else { + values = append(values, "keyctl=0") + } + } + + if r.MountTypes != nil { + if len(*r.MountTypes) > 0 { + values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountTypes, ";"))) + } + } + + if r.Nesting != nil { + if *r.Nesting { + values = append(values, "nesting=1") + } else { + values = append(values, "nesting=0") + } + } + + if len(values) > 0 { + v.Add(key, strings.Join(values, ",")) + } + + return nil +} + +// EncodeValues converts a VirtualEnvironmentContainerCustomMountPoint struct to a URL vlaue. +func (r VirtualEnvironmentContainerCustomMountPoint) EncodeValues(key string, v *url.Values) error { + values := []string{} + + if r.ACL != nil { + if *r.ACL { + values = append(values, "acl=%d") + } else { + values = append(values, "acl=0") + } + } + + if r.Backup != nil { + if *r.Backup { + values = append(values, "backup=1") + } else { + values = append(values, "backup=0") + } + } + + if r.DiskSize != nil { + values = append(values, fmt.Sprintf("size=%d", *r.DiskSize)) + } + + if r.MountOptions != nil { + if len(*r.MountOptions) > 0 { + values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";"))) + } + } + + values = append(values, fmt.Sprintf("mp=%s", r.MountPoint)) + + if r.Quota != nil { + if *r.Quota { + values = append(values, "quota=1") + } else { + values = append(values, "quota=0") + } + } + + if r.ReadOnly != nil { + if *r.ReadOnly { + values = append(values, "ro=1") + } else { + values = append(values, "ro=0") + } + } + + if r.Replicate != nil { + if *r.ReadOnly { + values = append(values, "replicate=1") + } else { + values = append(values, "replicate=0") + } + } + + if r.Shared != nil { + if *r.Shared { + values = append(values, "shared=1") + } else { + values = append(values, "shared=0") + } + } + + values = append(values, fmt.Sprintf("volume=%s", r.Volume)) + + if len(values) > 0 { + v.Add(key, strings.Join(values, ",")) + } + + return nil +} + +// EncodeValues converts a VirtualEnvironmentContainerCustomNetworkInterface struct to a URL vlaue. +func (r VirtualEnvironmentContainerCustomNetworkInterface) EncodeValues(key string, v *url.Values) error { + values := []string{} + + if r.Bridge != nil { + values = append(values, fmt.Sprintf("bridge=%s", *r.Bridge)) + } + + if r.Firewall != nil { + if *r.Firewall { + values = append(values, "firewall=1") + } else { + values = append(values, "firewall=0") + } + } + + if r.IPv4Address != nil { + values = append(values, fmt.Sprintf("ip=%s", *r.IPv4Address)) + } + + if r.IPv4Gateway != nil { + values = append(values, fmt.Sprintf("gw=%s", *r.IPv4Gateway)) + } + + if r.IPv6Address != nil { + values = append(values, fmt.Sprintf("ip6=%s", *r.IPv6Address)) + } + + if r.IPv6Gateway != nil { + values = append(values, fmt.Sprintf("gw6=%s", *r.IPv6Gateway)) + } + + if r.MACAddress != nil { + values = append(values, fmt.Sprintf("hwaddr=%s", *r.MACAddress)) + } + + if r.MTU != nil { + values = append(values, fmt.Sprintf("mtu=%d", *r.MTU)) + } + + values = append(values, fmt.Sprintf("name=%s", r.Name)) + + if r.RateLimit != nil { + values = append(values, fmt.Sprintf("rate=%d", *r.RateLimit)) + } + + if r.Tag != nil { + values = append(values, fmt.Sprintf("tag=%d", *r.Tag)) + } + + if r.Trunks != nil && len(*r.Trunks) > 0 { + sTrunks := make([]string, len(*r.Trunks)) + + for i, v := range *r.Trunks { + sTrunks[i] = strconv.Itoa(v) + } + + values = append(values, fmt.Sprintf("trunks=%s", strings.Join(sTrunks, ";"))) + } + + if r.Type != nil { + values = append(values, fmt.Sprintf("type=%s", *r.Type)) + } + + if len(values) > 0 { + v.Add(key, strings.Join(values, ",")) + } + + return nil +} + +// EncodeValues converts a VirtualEnvironmentContainerCustomRootFS struct to a URL vlaue. +func (r VirtualEnvironmentContainerCustomRootFS) EncodeValues(key string, v *url.Values) error { + values := []string{} + + if r.ACL != nil { + if *r.ACL { + values = append(values, "acl=%d") + } else { + values = append(values, "acl=0") + } + } + + if r.DiskSize != nil { + values = append(values, fmt.Sprintf("size=%d", *r.DiskSize)) + } + + if r.MountOptions != nil { + if len(*r.MountOptions) > 0 { + values = append(values, fmt.Sprintf("mount=%s", strings.Join(*r.MountOptions, ";"))) + } + } + + if r.Quota != nil { + if *r.Quota { + values = append(values, "quota=1") + } else { + values = append(values, "quota=0") + } + } + + if r.ReadOnly != nil { + if *r.ReadOnly { + values = append(values, "ro=1") + } else { + values = append(values, "ro=0") + } + } + + if r.Replicate != nil { + if *r.ReadOnly { + values = append(values, "replicate=1") + } else { + values = append(values, "replicate=0") + } + } + + if r.Shared != nil { + if *r.Shared { + values = append(values, "shared=1") + } else { + values = append(values, "shared=0") + } + } + + values = append(values, fmt.Sprintf("volume=%s", r.Volume)) + + if len(values) > 0 { + v.Add(key, strings.Join(values, ",")) + } + + return nil +} + +// 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")) + + return nil +} + +// EncodeValues converts a VirtualEnvironmentContainerCustomStartupBehavior struct to a URL vlaue. +func (r VirtualEnvironmentContainerCustomStartupBehavior) EncodeValues(key string, v *url.Values) error { + values := []string{} + + if r.Down != nil { + values = append(values, fmt.Sprintf("down=%d", *r.Down)) + } + + if r.Order != nil { + values = append(values, fmt.Sprintf("order=%d", *r.Order)) + } + + if r.Up != nil { + values = append(values, fmt.Sprintf("up=%d", *r.Up)) + } + + if len(values) > 0 { + v.Add(key, strings.Join(values, ",")) + } + + return nil +} + +// UnmarshalJSON converts a VirtualEnvironmentContainerCustomFeatures string to an object. +func (r *VirtualEnvironmentContainerCustomFeatures) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + switch v[0] { + case "fuse": + bv := CustomBool(v[1] == "1") + r.FUSE = &bv + case "keyctl": + bv := CustomBool(v[1] == "1") + r.KeyControl = &bv + case "mount": + if v[1] != "" { + a := strings.Split(v[1], ";") + r.MountTypes = &a + } else { + a := []string{} + r.MountTypes = &a + } + case "nesting": + bv := CustomBool(v[1] == "1") + r.Nesting = &bv + } + } + } + + return nil +} + +// UnmarshalJSON converts a VirtualEnvironmentContainerCustomMountPoint string to an object. +func (r *VirtualEnvironmentContainerCustomMountPoint) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 1 { + r.Volume = v[1] + } else if len(v) == 2 { + switch v[0] { + case "acl": + bv := CustomBool(v[1] == "1") + r.ACL = &bv + case "backup": + bv := CustomBool(v[1] == "1") + r.Backup = &bv + case "mountoptions": + if v[1] != "" { + a := strings.Split(v[1], ";") + r.MountOptions = &a + } else { + a := []string{} + r.MountOptions = &a + } + case "mp": + r.MountPoint = v[1] + case "quota": + bv := CustomBool(v[1] == "1") + r.Quota = &bv + case "ro": + bv := CustomBool(v[1] == "1") + r.ReadOnly = &bv + case "replicate": + bv := CustomBool(v[1] == "1") + r.Replicate = &bv + case "shared": + bv := CustomBool(v[1] == "1") + r.Shared = &bv + case "size": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.DiskSize = &iv + } + } + } + + return nil +} + +// UnmarshalJSON converts a VirtualEnvironmentContainerCustomNetworkInterface string to an object. +func (r *VirtualEnvironmentContainerCustomNetworkInterface) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 1 { + r.Name = v[1] + } else if len(v) == 2 { + switch v[0] { + case "bridge": + r.Bridge = &v[1] + case "firewall": + bv := CustomBool(v[1] == "1") + r.Firewall = &bv + case "gw": + r.IPv4Gateway = &v[1] + case "gw6": + r.IPv6Gateway = &v[1] + case "ip": + r.IPv4Address = &v[1] + case "ip6": + r.IPv6Address = &v[1] + case "hwaddr": + r.MACAddress = &v[1] + case "mtu": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.MTU = &iv + case "name": + r.Name = v[1] + case "rate": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.RateLimit = &iv + case "tag": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.Tag = &iv + case "trunks": + if v[1] != "" { + trunks := strings.Split(v[1], ";") + a := make([]int, len(trunks)) + + for ti, tv := range trunks { + a[ti], err = strconv.Atoi(tv) + + if err != nil { + return err + } + } + + r.Trunks = &a + } else { + a := []int{} + r.Trunks = &a + } + case "type": + r.Type = &v[1] + } + } + } + + return nil +} + +// UnmarshalJSON converts a VirtualEnvironmentContainerCustomRootFS string to an object. +func (r *VirtualEnvironmentContainerCustomRootFS) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 1 { + r.Volume = v[1] + } else if len(v) == 2 { + switch v[0] { + case "acl": + bv := CustomBool(v[1] == "1") + r.ACL = &bv + case "mountoptions": + if v[1] != "" { + a := strings.Split(v[1], ";") + r.MountOptions = &a + } else { + a := []string{} + r.MountOptions = &a + } + case "quota": + bv := CustomBool(v[1] == "1") + r.Quota = &bv + case "ro": + bv := CustomBool(v[1] == "1") + r.ReadOnly = &bv + case "replicate": + bv := CustomBool(v[1] == "1") + r.Replicate = &bv + case "shared": + bv := CustomBool(v[1] == "1") + r.Shared = &bv + case "size": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.DiskSize = &iv + } + } + } + + return nil +} + +// UnmarshalJSON converts a VirtualEnvironmentContainerCustomStartupBehavior string to an object. +func (r *VirtualEnvironmentContainerCustomStartupBehavior) UnmarshalJSON(b []byte) error { + var s string + + err := json.Unmarshal(b, &s) + + if err != nil { + return err + } + + pairs := strings.Split(s, ",") + + for _, p := range pairs { + v := strings.Split(strings.TrimSpace(p), "=") + + if len(v) == 2 { + switch v[0] { + case "down": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.Down = &iv + case "order": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.Order = &iv + case "up": + iv, err := strconv.Atoi(v[1]) + + if err != nil { + return err + } + + r.Up = &iv + } + } + } + + return nil +} diff --git a/proxmox/virtual_environment_vm.go b/proxmox/virtual_environment_vm.go index f8e6e93f..31f6afb3 100644 --- a/proxmox/virtual_environment_vm.go +++ b/proxmox/virtual_environment_vm.go @@ -133,8 +133,8 @@ func (c *VirtualEnvironmentClient) UpdateVMAsync(nodeName string, vmID int, d *V return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/qemu/%d/config", url.PathEscape(nodeName), vmID), d, nil) } -// WaitForNetworkInterfacesFromAgent waits for a virtual machine's QEMU agent to publish the network interfaces. -func (c *VirtualEnvironmentClient) WaitForNetworkInterfacesFromAgent(nodeName string, vmID int, timeout int, delay int) (*VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData, error) { +// WaitForNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to publish the network interfaces. +func (c *VirtualEnvironmentClient) WaitForNetworkInterfacesFromVMAgent(nodeName string, vmID int, timeout int, delay int) (*VirtualEnvironmentVMGetQEMUNetworkInterfacesResponseData, error) { timeDelay := int64(delay) timeMax := float64(timeout) timeStart := time.Now() @@ -159,8 +159,8 @@ func (c *VirtualEnvironmentClient) WaitForNetworkInterfacesFromAgent(nodeName st return nil, fmt.Errorf("Timeout while waiting for the QEMU agent on VM \"%d\" to publish the network interfaces", vmID) } -// WaitForNoNetworkInterfacesFromAgent waits for a virtual machine's QEMU agent to unpublish the network interfaces. -func (c *VirtualEnvironmentClient) WaitForNoNetworkInterfacesFromAgent(nodeName string, vmID int, timeout int, delay int) error { +// WaitForNoNetworkInterfacesFromVMAgent waits for a virtual machine's QEMU agent to unpublish the network interfaces. +func (c *VirtualEnvironmentClient) WaitForNoNetworkInterfacesFromVMAgent(nodeName string, vmID int, timeout int, delay int) error { timeDelay := int64(delay) timeMax := float64(timeout) timeStart := time.Now() @@ -185,8 +185,8 @@ func (c *VirtualEnvironmentClient) WaitForNoNetworkInterfacesFromAgent(nodeName return fmt.Errorf("Timeout while waiting for the QEMU agent on VM \"%d\" to unpublish the network interfaces", vmID) } -// WaitForState waits for a virtual machine to reach a specific state. -func (c *VirtualEnvironmentClient) WaitForState(nodeName string, vmID int, state string, timeout int, delay int) error { +// WaitForVMState waits for a virtual machine to reach a specific state. +func (c *VirtualEnvironmentClient) WaitForVMState(nodeName string, vmID int, state string, timeout int, delay int) error { state = strings.ToLower(state) timeDelay := int64(delay) diff --git a/proxmoxtf/resource_virtual_environment_container.go b/proxmoxtf/resource_virtual_environment_container.go new file mode 100644 index 00000000..2d25c239 --- /dev/null +++ b/proxmoxtf/resource_virtual_environment_container.go @@ -0,0 +1,888 @@ +/* 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 ( + "strconv" + "strings" + + "github.com/danitso/terraform-provider-proxmox/proxmox" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +const ( + dvResourceVirtualEnvironmentContainerConsoleEnabled = true + dvResourceVirtualEnvironmentContainerConsoleMode = "tty" + dvResourceVirtualEnvironmentContainerConsoleTTYCount = 2 + dvResourceVirtualEnvironmentContainerInitializationDNSDomain = "" + dvResourceVirtualEnvironmentContainerInitializationDNSServer = "" + dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address = "" + dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway = "" + dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address = "" + dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway = "" + dvResourceVirtualEnvironmentContainerInitializationHostname = "" + dvResourceVirtualEnvironmentContainerInitializationUserAccountPassword = "" + dvResourceVirtualEnvironmentContainerCPUArchitecture = "amd64" + dvResourceVirtualEnvironmentContainerCPUCores = 1 + dvResourceVirtualEnvironmentContainerCPUUnits = 1024 + dvResourceVirtualEnvironmentContainerDescription = "" + dvResourceVirtualEnvironmentContainerDiskDatastoreID = "local-lvm" + dvResourceVirtualEnvironmentContainerDiskFileFormat = "qcow2" + dvResourceVirtualEnvironmentContainerDiskFileID = "" + dvResourceVirtualEnvironmentContainerDiskSize = 8 + dvResourceVirtualEnvironmentContainerDiskSpeedRead = 0 + dvResourceVirtualEnvironmentContainerDiskSpeedReadBurstable = 0 + dvResourceVirtualEnvironmentContainerDiskSpeedWrite = 0 + dvResourceVirtualEnvironmentContainerDiskSpeedWriteBurstable = 0 + dvResourceVirtualEnvironmentContainerMemoryDedicated = 512 + dvResourceVirtualEnvironmentContainerMemorySwap = 0 + dvResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "vmbr0" + dvResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = true + dvResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress = "" + dvResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit = 0 + dvResourceVirtualEnvironmentContainerNetworkInterfaceVLANID = 0 + dvResourceVirtualEnvironmentContainerOperatingSystemType = "unmanaged" + dvResourceVirtualEnvironmentContainerPoolID = "" + dvResourceVirtualEnvironmentContainerStarted = true + dvResourceVirtualEnvironmentContainerVMID = -1 + + mkResourceVirtualEnvironmentContainerConsole = "console" + mkResourceVirtualEnvironmentContainerConsoleEnabled = "enabled" + mkResourceVirtualEnvironmentContainerConsoleMode = "type" + mkResourceVirtualEnvironmentContainerConsoleTTYCount = "tty_count" + mkResourceVirtualEnvironmentContainerCPU = "cpu" + mkResourceVirtualEnvironmentContainerCPUArchitecture = "architecture" + mkResourceVirtualEnvironmentContainerCPUCores = "cores" + mkResourceVirtualEnvironmentContainerCPUUnits = "units" + mkResourceVirtualEnvironmentContainerDescription = "description" + mkResourceVirtualEnvironmentContainerInitialization = "initialization" + mkResourceVirtualEnvironmentContainerInitializationDNS = "dns" + mkResourceVirtualEnvironmentContainerInitializationDNSDomain = "domain" + mkResourceVirtualEnvironmentContainerInitializationDNSServer = "server" + mkResourceVirtualEnvironmentContainerInitializationHostname = "hostname" + mkResourceVirtualEnvironmentContainerInitializationIPConfig = "ip_config" + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4 = "ipv4" + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address = "address" + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway = "gateway" + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6 = "ipv6" + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address = "address" + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway = "gateway" + mkResourceVirtualEnvironmentContainerInitializationUserAccount = "user_account" + mkResourceVirtualEnvironmentContainerInitializationUserAccountKeys = "keys" + mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword = "password" + mkResourceVirtualEnvironmentContainerInitializationUserAccountUsername = "username" + mkResourceVirtualEnvironmentContainerMemory = "memory" + mkResourceVirtualEnvironmentContainerMemoryDedicated = "dedicated" + mkResourceVirtualEnvironmentContainerMemorySwap = "swap" + mkResourceVirtualEnvironmentContainerNetworkInterface = "network_device" + mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "bridge" + mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = "enabled" + mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress = "mac_address" + mkResourceVirtualEnvironmentContainerNetworkInterfaceName = "name" + mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit = "rate_limit" + mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID = "vlan_id" + mkResourceVirtualEnvironmentContainerNodeName = "node_name" + mkResourceVirtualEnvironmentContainerOperatingSystem = "operating_system" + mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID = "template_file_id" + mkResourceVirtualEnvironmentContainerOperatingSystemType = "type" + mkResourceVirtualEnvironmentContainerPoolID = "pool_id" + mkResourceVirtualEnvironmentContainerStarted = "started" + mkResourceVirtualEnvironmentContainerVMID = "vm_id" +) + +func resourceVirtualEnvironmentContainer() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerConsole: &schema.Schema{ + Type: schema.TypeList, + Description: "The console configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + defaultList := make([]interface{}, 1) + defaultMap := map[string]interface{}{} + + defaultMap[mkResourceVirtualEnvironmentContainerConsoleEnabled] = dvResourceVirtualEnvironmentContainerConsoleEnabled + defaultMap[mkResourceVirtualEnvironmentContainerConsoleMode] = dvResourceVirtualEnvironmentContainerConsoleMode + defaultMap[mkResourceVirtualEnvironmentContainerConsoleTTYCount] = dvResourceVirtualEnvironmentContainerConsoleTTYCount + + defaultList[0] = defaultMap + + return defaultList, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerConsoleEnabled: { + Type: schema.TypeBool, + Description: "Whether to enable the console device", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerConsoleEnabled, + }, + mkResourceVirtualEnvironmentContainerConsoleMode: { + Type: schema.TypeString, + Description: "The console mode", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerConsoleMode, + ValidateFunc: resourceVirtualEnvironmentContainerGetConsoleModeValidator(), + }, + mkResourceVirtualEnvironmentContainerConsoleTTYCount: { + Type: schema.TypeInt, + Description: "The number of available TTY", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerConsoleTTYCount, + ValidateFunc: validation.IntBetween(0, 6), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerCPU: &schema.Schema{ + Type: schema.TypeList, + Description: "The CPU allocation", + Optional: true, + DefaultFunc: func() (interface{}, error) { + defaultList := make([]interface{}, 1) + defaultMap := map[string]interface{}{} + + defaultMap[mkResourceVirtualEnvironmentContainerCPUArchitecture] = dvResourceVirtualEnvironmentContainerCPUArchitecture + defaultMap[mkResourceVirtualEnvironmentContainerCPUCores] = dvResourceVirtualEnvironmentContainerCPUCores + defaultMap[mkResourceVirtualEnvironmentContainerCPUUnits] = dvResourceVirtualEnvironmentContainerCPUUnits + + defaultList[0] = defaultMap + + return defaultList, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerCPUArchitecture: { + Type: schema.TypeString, + Description: "The CPU architecture", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerCPUArchitecture, + ValidateFunc: resourceVirtualEnvironmentContainerGetCPUArchitectureValidator(), + }, + mkResourceVirtualEnvironmentContainerCPUCores: { + Type: schema.TypeInt, + Description: "The number of CPU cores", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerCPUCores, + ValidateFunc: validation.IntBetween(1, 128), + }, + mkResourceVirtualEnvironmentContainerCPUUnits: { + Type: schema.TypeInt, + Description: "The CPU units", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerCPUUnits, + ValidateFunc: validation.IntBetween(0, 500000), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerDescription: { + Type: schema.TypeString, + Description: "The description", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerDescription, + }, + mkResourceVirtualEnvironmentContainerInitialization: &schema.Schema{ + Type: schema.TypeList, + Description: "The initialization configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerInitializationDNS: { + Type: schema.TypeList, + Description: "The DNS configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerInitializationDNSDomain: { + Type: schema.TypeString, + Description: "The DNS search domain", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationDNSDomain, + }, + mkResourceVirtualEnvironmentContainerInitializationDNSServer: { + Type: schema.TypeString, + Description: "The DNS server", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationDNSServer, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerInitializationHostname: { + Type: schema.TypeString, + Description: "The hostname", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationHostname, + }, + mkResourceVirtualEnvironmentContainerInitializationIPConfig: { + Type: schema.TypeList, + Description: "The IP configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4: { + Type: schema.TypeList, + Description: "The IPv4 configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address: { + Type: schema.TypeString, + Description: "The IPv4 address", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address, + }, + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway: { + Type: schema.TypeString, + Description: "The IPv4 gateway", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6: { + Type: schema.TypeList, + Description: "The IPv6 configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address: { + Type: schema.TypeString, + Description: "The IPv6 address", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address, + }, + mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway: { + Type: schema.TypeString, + Description: "The IPv6 gateway", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + }, + }, + MaxItems: 8, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerInitializationUserAccount: { + Type: schema.TypeList, + Description: "The user account configuration", + Optional: true, + ForceNew: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerInitializationUserAccountKeys: { + Type: schema.TypeList, + Description: "The SSH keys", + Optional: true, + ForceNew: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword: { + Type: schema.TypeString, + Description: "The SSH password", + Optional: true, + ForceNew: true, + Sensitive: true, + Default: dvResourceVirtualEnvironmentContainerInitializationUserAccountPassword, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.ReplaceAll(old, "*", "") == "" + }, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerMemory: &schema.Schema{ + Type: schema.TypeList, + Description: "The memory allocation", + Optional: true, + DefaultFunc: func() (interface{}, error) { + defaultList := make([]interface{}, 1) + defaultMap := map[string]interface{}{} + + defaultMap[mkResourceVirtualEnvironmentContainerMemoryDedicated] = dvResourceVirtualEnvironmentContainerMemoryDedicated + defaultMap[mkResourceVirtualEnvironmentContainerMemorySwap] = dvResourceVirtualEnvironmentContainerMemorySwap + + defaultList[0] = defaultMap + + return defaultList, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerMemoryDedicated: { + Type: schema.TypeInt, + Description: "The dedicated memory in megabytes", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMemoryDedicated, + ValidateFunc: validation.IntBetween(16, 268435456), + }, + mkResourceVirtualEnvironmentContainerMemorySwap: { + Type: schema.TypeInt, + Description: "The swap size in megabytes", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerMemorySwap, + ValidateFunc: validation.IntBetween(0, 268435456), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerNetworkInterface: &schema.Schema{ + Type: schema.TypeList, + Description: "The network interfaces", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return make([]interface{}, 1), nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge: { + Type: schema.TypeString, + Description: "The bridge", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceBridge, + }, + mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled: { + Type: schema.TypeBool, + Description: "Whether to enable the network device", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceEnabled, + }, + mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress: { + Type: schema.TypeString, + Description: "The MAC address", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return new == "" + }, + ValidateFunc: getMACAddressValidator(), + }, + mkResourceVirtualEnvironmentContainerNetworkInterfaceName: { + Type: schema.TypeString, + Description: "The network interface name", + Required: true, + }, + mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit: { + Type: schema.TypeFloat, + Description: "The rate limit in megabytes per second", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit, + }, + mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID: { + Type: schema.TypeInt, + Description: "The VLAN identifier", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceVLANID, + }, + }, + }, + MaxItems: 8, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerNodeName: &schema.Schema{ + Type: schema.TypeString, + Description: "The node name", + Required: true, + ForceNew: true, + }, + mkResourceVirtualEnvironmentContainerOperatingSystem: &schema.Schema{ + Type: schema.TypeList, + Description: "The operating system configuration", + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID: { + Type: schema.TypeString, + Description: "The ID of an OS template file", + Required: true, + ForceNew: true, + ValidateFunc: getFileIDValidator(), + }, + mkResourceVirtualEnvironmentContainerOperatingSystemType: { + Type: schema.TypeString, + Description: "The type", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerOperatingSystemType, + ValidateFunc: resourceVirtualEnvironmentContainerGetOperatingSystemTypeValidator(), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentContainerPoolID: { + Type: schema.TypeString, + Description: "The ID of the pool to assign the virtual machine to", + Optional: true, + ForceNew: true, + Default: dvResourceVirtualEnvironmentContainerPoolID, + }, + mkResourceVirtualEnvironmentContainerStarted: { + Type: schema.TypeBool, + Description: "Whether to start the container", + Optional: true, + Default: dvResourceVirtualEnvironmentContainerStarted, + }, + mkResourceVirtualEnvironmentContainerVMID: { + Type: schema.TypeInt, + Description: "The VM identifier", + Optional: true, + ForceNew: true, + Default: dvResourceVirtualEnvironmentContainerVMID, + ValidateFunc: getVMIDValidator(), + }, + }, + Create: resourceVirtualEnvironmentContainerCreate, + Read: resourceVirtualEnvironmentContainerRead, + Update: resourceVirtualEnvironmentContainerUpdate, + Delete: resourceVirtualEnvironmentContainerDelete, + } +} + +func resourceVirtualEnvironmentContainerCreate(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string) + resource := resourceVirtualEnvironmentContainer() + + 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) + + 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) + + description := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string) + + initialization := d.Get(mkResourceVirtualEnvironmentContainerInitialization).([]interface{}) + initializationDNSDomain := dvResourceVirtualEnvironmentContainerInitializationDNSDomain + initializationDNSServer := dvResourceVirtualEnvironmentContainerInitializationDNSServer + initializationHostname := dvResourceVirtualEnvironmentContainerInitializationHostname + initializationIPConfigIPv4Address := []string{} + initializationIPConfigIPv4Gateway := []string{} + initializationIPConfigIPv6Address := []string{} + initializationIPConfigIPv6Gateway := []string{} + initializationUserAccountKeys := proxmox.VirtualEnvironmentContainerCustomSSHKeys{} + initializationUserAccountPassword := dvResourceVirtualEnvironmentContainerInitializationUserAccountPassword + + if len(initialization) > 0 { + initializationBlock := initialization[0].(map[string]interface{}) + initializationDNS := initializationBlock[mkResourceVirtualEnvironmentContainerInitializationDNS].([]interface{}) + + if len(initializationDNS) > 0 { + initializationDNSBlock := initializationDNS[0].(map[string]interface{}) + initializationDNSDomain = initializationDNSBlock[mkResourceVirtualEnvironmentContainerInitializationDNSDomain].(string) + initializationDNSServer = initializationDNSBlock[mkResourceVirtualEnvironmentContainerInitializationDNSServer].(string) + } + + initializationHostname = initializationBlock[mkResourceVirtualEnvironmentContainerInitializationHostname].(string) + initializationIPConfig := initializationBlock[mkResourceVirtualEnvironmentContainerInitializationIPConfig].([]interface{}) + + for _, c := range initializationIPConfig { + configBlock := c.(map[string]interface{}) + ipv4 := configBlock[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4].([]interface{}) + + if len(ipv4) > 0 { + ipv4Block := ipv4[0].(map[string]interface{}) + + initializationIPConfigIPv4Address = append(initializationIPConfigIPv4Address, ipv4Block[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address].(string)) + initializationIPConfigIPv4Gateway = append(initializationIPConfigIPv4Gateway, ipv4Block[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway].(string)) + } else { + initializationIPConfigIPv4Address = append(initializationIPConfigIPv4Address, "") + initializationIPConfigIPv4Gateway = append(initializationIPConfigIPv4Gateway, "") + } + + ipv6 := configBlock[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6].([]interface{}) + + if len(ipv6) > 0 { + ipv6Block := ipv6[0].(map[string]interface{}) + + initializationIPConfigIPv6Address = append(initializationIPConfigIPv6Address, ipv6Block[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address].(string)) + initializationIPConfigIPv6Gateway = append(initializationIPConfigIPv6Gateway, ipv6Block[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway].(string)) + } else { + initializationIPConfigIPv6Address = append(initializationIPConfigIPv6Address, "") + initializationIPConfigIPv6Gateway = append(initializationIPConfigIPv6Gateway, "") + } + } + + initializationUserAccount := initializationBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccount].([]interface{}) + + if len(initializationUserAccount) > 0 { + initializationUserAccountBlock := initializationUserAccount[0].(map[string]interface{}) + + keys := initializationUserAccountBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccountKeys].([]interface{}) + initializationUserAccountKeys := make(proxmox.VirtualEnvironmentContainerCustomSSHKeys, len(keys)) + + for ki, kv := range keys { + initializationUserAccountKeys[ki] = kv.(string) + } + + initializationUserAccountPassword = initializationUserAccountBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword].(string) + } + } + + memoryBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentContainerMemory}, 0, true) + + if err != nil { + return err + } + + memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentContainerMemoryDedicated].(int) + memorySwap := memoryBlock[mkResourceVirtualEnvironmentContainerMemorySwap].(int) + + networkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{}) + networkInterfaceArray := make(proxmox.VirtualEnvironmentContainerCustomNetworkInterfaceArray, len(networkInterface)) + + for ni, nv := range networkInterface { + networkInterfaceMap := nv.(map[string]interface{}) + networkInterfaceObject := proxmox.VirtualEnvironmentContainerCustomNetworkInterface{} + + bridge := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge].(string) + enabled := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled].(bool) + macAddress := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress].(string) + name := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceName].(string) + rateLimit := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit].(int) + vlanID := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID].(int) + + if bridge != "" { + networkInterfaceObject.Bridge = &bridge + } + + networkInterfaceObject.Enabled = enabled + + if len(initializationIPConfigIPv4Address) > ni { + if initializationIPConfigIPv4Address[ni] != "" { + networkInterfaceObject.IPv4Address = &initializationIPConfigIPv4Address[ni] + } + + if initializationIPConfigIPv4Gateway[ni] != "" { + networkInterfaceObject.IPv4Gateway = &initializationIPConfigIPv4Gateway[ni] + } + + if initializationIPConfigIPv6Address[ni] != "" { + networkInterfaceObject.IPv6Address = &initializationIPConfigIPv6Address[ni] + } + + if initializationIPConfigIPv6Gateway[ni] != "" { + networkInterfaceObject.IPv6Gateway = &initializationIPConfigIPv6Gateway[ni] + } + } + + if macAddress != "" { + networkInterfaceObject.MACAddress = &macAddress + } + + if name != "" { + networkInterfaceObject.Name = name + } + + if rateLimit != 0 { + networkInterfaceObject.RateLimit = &rateLimit + } + + if vlanID != 0 { + networkInterfaceObject.Tag = &vlanID + } + + networkInterfaceArray[ni] = networkInterfaceObject + } + + operatingSystem, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentContainerOperatingSystem}, 0, true) + + if err != nil { + return err + } + + operatingSystemTemplateFileID := operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID].(string) + operatingSystemType := operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType].(string) + + poolID := d.Get(mkResourceVirtualEnvironmentContainerPoolID).(string) + started := proxmox.CustomBool(d.Get(mkResourceVirtualEnvironmentContainerStarted).(bool)) + vmID := d.Get(mkResourceVirtualEnvironmentContainerVMID).(int) + + if vmID == -1 { + vmIDNew, err := veClient.GetVMID() + + if err != nil { + return err + } + + vmID = *vmIDNew + } + + // Attempt to create the resource using the retrieved values. + body := proxmox.VirtualEnvironmentContainerCreateRequestBody{ + ConsoleEnabled: &consoleEnabled, + ConsoleMode: &consoleMode, + CPUArchitecture: &cpuArchitecture, + CPUCores: &cpuCores, + CPUUnits: &cpuUnits, + DedicatedMemory: &memoryDedicated, + NetworkInterfaces: networkInterfaceArray, + OSTemplateFileVolume: operatingSystemTemplateFileID, + OSType: &operatingSystemType, + StartOnBoot: &started, + Swap: &memorySwap, + TTY: &consoleTTYCount, + VMID: vmID, + } + + if description != "" { + body.Description = &description + } + + if initializationDNSDomain != "" { + body.DNSDomain = &initializationDNSDomain + } + + if initializationDNSServer != "" { + body.DNSServer = &initializationDNSServer + } + + if initializationHostname != "" { + body.Hostname = &initializationHostname + } + + if len(initializationUserAccountKeys) > 0 { + body.SSHKeys = &initializationUserAccountKeys + } + + if initializationUserAccountPassword != "" { + body.Password = &initializationUserAccountPassword + } + + if poolID != "" { + body.PoolID = &poolID + } + + err = veClient.CreateContainer(nodeName, &body) + + if err != nil { + return err + } + + d.SetId(strconv.Itoa(vmID)) + + return resourceVirtualEnvironmentContainerCreateStart(d, m) +} + +func resourceVirtualEnvironmentContainerCreateStart(d *schema.ResourceData, m interface{}) error { + started := d.Get(mkResourceVirtualEnvironmentContainerStarted).(bool) + + if !started { + return resourceVirtualEnvironmentContainerRead(d, m) + } + + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string) + vmID, err := strconv.Atoi(d.Id()) + + if err != nil { + return err + } + + // Start the container and wait for it to reach a running state before continuing. + err = veClient.StartContainer(nodeName, vmID) + + if err != nil { + return err + } + + err = veClient.WaitForContainerState(nodeName, vmID, "running", 120, 5) + + if err != nil { + return err + } + + return resourceVirtualEnvironmentContainerRead(d, m) +} + +func resourceVirtualEnvironmentContainerGetConsoleModeValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "console", + "shell", + "tty", + }, false) +} + +func resourceVirtualEnvironmentContainerGetCPUArchitectureValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "amd64", + "arm64", + "armhf", + "i386", + }, false) +} + +func resourceVirtualEnvironmentContainerGetOperatingSystemTypeValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "alpine", + "archlinux", + "centos", + "debian", + "fedora", + "gentoo", + "opensuse", + "ubuntu", + "unmanaged", + }, false) +} + +func resourceVirtualEnvironmentContainerRead(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string) + vmID, err := strconv.Atoi(d.Id()) + + if err != nil { + return err + } + + // Retrieve the entire configuration in order to compare it to the state. + _, err = veClient.GetContainer(nodeName, vmID) + + if err != nil { + if strings.Contains(err.Error(), "HTTP 404") { + d.SetId("") + + return nil + } + + return err + } + + return nil +} + +func resourceVirtualEnvironmentContainerUpdate(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string) + vmID, err := strconv.Atoi(d.Id()) + + if err != nil { + return err + } + + // Retrieve the entire configuration as we need to process certain values. + _, err = veClient.GetContainer(nodeName, vmID) + + if err != nil { + return err + } + + return resourceVirtualEnvironmentContainerRead(d, m) +} + +func resourceVirtualEnvironmentContainerDelete(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string) + vmID, err := strconv.Atoi(d.Id()) + + if err != nil { + return err + } + + err = veClient.DeleteContainer(nodeName, vmID) + + if err != nil { + if strings.Contains(err.Error(), "HTTP 404") { + d.SetId("") + + return nil + } + + return err + } + + d.SetId("") + + return nil +} diff --git a/proxmoxtf/resource_virtual_environment_hosts.go b/proxmoxtf/resource_virtual_environment_hosts.go index 5cbd0440..c3125afd 100644 --- a/proxmoxtf/resource_virtual_environment_hosts.go +++ b/proxmoxtf/resource_virtual_environment_hosts.go @@ -79,6 +79,7 @@ func resourceVirtualEnvironmentHosts() *schema.Resource { }, }, }, + MinItems: 1, }, mkResourceVirtualEnvironmentHostsHostnames: &schema.Schema{ Type: schema.TypeList, diff --git a/proxmoxtf/resource_virtual_environment_vm.go b/proxmoxtf/resource_virtual_environment_vm.go index 98b237f2..f5813663 100644 --- a/proxmoxtf/resource_virtual_environment_vm.go +++ b/proxmoxtf/resource_virtual_environment_vm.go @@ -16,121 +16,128 @@ import ( ) const ( - dvResourceVirtualEnvironmentVMACPI = true - dvResourceVirtualEnvironmentVMAgentEnabled = false - dvResourceVirtualEnvironmentVMAgentTrim = false - dvResourceVirtualEnvironmentVMAgentType = "virtio" - dvResourceVirtualEnvironmentVMBIOS = "seabios" - dvResourceVirtualEnvironmentVMCDROMEnabled = false - dvResourceVirtualEnvironmentVMCDROMFileID = "" - dvResourceVirtualEnvironmentVMCloudInitDNSDomain = "" - dvResourceVirtualEnvironmentVMCloudInitDNSServer = "" - dvResourceVirtualEnvironmentVMCloudInitUserAccountPassword = "" - dvResourceVirtualEnvironmentVMCloudInitUserDataFileID = "" - dvResourceVirtualEnvironmentVMCPUCores = 1 - dvResourceVirtualEnvironmentVMCPUHotplugged = 0 - dvResourceVirtualEnvironmentVMCPUSockets = 1 - dvResourceVirtualEnvironmentVMCPUType = "qemu64" - dvResourceVirtualEnvironmentVMCPUUnits = 1024 - dvResourceVirtualEnvironmentVMDescription = "" - dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm" - dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2" - dvResourceVirtualEnvironmentVMDiskFileID = "" - dvResourceVirtualEnvironmentVMDiskSize = 8 - dvResourceVirtualEnvironmentVMDiskSpeedRead = 0 - dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable = 0 - dvResourceVirtualEnvironmentVMDiskSpeedWrite = 0 - dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = 0 - dvResourceVirtualEnvironmentVMKeyboardLayout = "en-us" - dvResourceVirtualEnvironmentVMMemoryDedicated = 512 - dvResourceVirtualEnvironmentVMMemoryFloating = 0 - dvResourceVirtualEnvironmentVMMemoryShared = 0 - dvResourceVirtualEnvironmentVMName = "" - dvResourceVirtualEnvironmentVMNetworkDeviceBridge = "vmbr0" - dvResourceVirtualEnvironmentVMNetworkDeviceEnabled = true - dvResourceVirtualEnvironmentVMNetworkDeviceMACAddress = "" - dvResourceVirtualEnvironmentVMNetworkDeviceModel = "virtio" - dvResourceVirtualEnvironmentVMNetworkDeviceRateLimit = 0 - dvResourceVirtualEnvironmentVMNetworkDeviceVLANID = 0 - dvResourceVirtualEnvironmentVMOSType = "other" - dvResourceVirtualEnvironmentVMPoolID = "" - dvResourceVirtualEnvironmentVMStarted = true - dvResourceVirtualEnvironmentVMTabletDevice = true - dvResourceVirtualEnvironmentVMVGAEnabled = true - dvResourceVirtualEnvironmentVMVGAMemory = 16 - dvResourceVirtualEnvironmentVMVGAType = "std" - dvResourceVirtualEnvironmentVMVMID = -1 + dvResourceVirtualEnvironmentVMACPI = true + dvResourceVirtualEnvironmentVMAgentEnabled = false + dvResourceVirtualEnvironmentVMAgentTrim = false + dvResourceVirtualEnvironmentVMAgentType = "virtio" + dvResourceVirtualEnvironmentVMBIOS = "seabios" + dvResourceVirtualEnvironmentVMCDROMEnabled = false + dvResourceVirtualEnvironmentVMCDROMFileID = "" + dvResourceVirtualEnvironmentVMCPUArchitecture = "x86_64" + dvResourceVirtualEnvironmentVMCPUCores = 1 + dvResourceVirtualEnvironmentVMCPUHotplugged = 0 + dvResourceVirtualEnvironmentVMCPUSockets = 1 + dvResourceVirtualEnvironmentVMCPUType = "qemu64" + dvResourceVirtualEnvironmentVMCPUUnits = 1024 + dvResourceVirtualEnvironmentVMDescription = "" + dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm" + dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2" + dvResourceVirtualEnvironmentVMDiskFileID = "" + dvResourceVirtualEnvironmentVMDiskSize = 8 + dvResourceVirtualEnvironmentVMDiskSpeedRead = 0 + dvResourceVirtualEnvironmentVMDiskSpeedReadBurstable = 0 + dvResourceVirtualEnvironmentVMDiskSpeedWrite = 0 + dvResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = 0 + dvResourceVirtualEnvironmentVMInitializationDNSDomain = "" + dvResourceVirtualEnvironmentVMInitializationDNSServer = "" + dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address = "" + dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway = "" + dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address = "" + dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway = "" + dvResourceVirtualEnvironmentVMInitializationUserAccountPassword = "" + dvResourceVirtualEnvironmentVMInitializationUserDataFileID = "" + dvResourceVirtualEnvironmentVMKeyboardLayout = "en-us" + dvResourceVirtualEnvironmentVMMemoryDedicated = 512 + dvResourceVirtualEnvironmentVMMemoryFloating = 0 + dvResourceVirtualEnvironmentVMMemoryShared = 0 + dvResourceVirtualEnvironmentVMName = "" + dvResourceVirtualEnvironmentVMNetworkDeviceBridge = "vmbr0" + dvResourceVirtualEnvironmentVMNetworkDeviceEnabled = true + dvResourceVirtualEnvironmentVMNetworkDeviceMACAddress = "" + dvResourceVirtualEnvironmentVMNetworkDeviceModel = "virtio" + dvResourceVirtualEnvironmentVMNetworkDeviceRateLimit = 0 + dvResourceVirtualEnvironmentVMNetworkDeviceVLANID = 0 + dvResourceVirtualEnvironmentVMOperatingSystemType = "other" + dvResourceVirtualEnvironmentVMPoolID = "" + dvResourceVirtualEnvironmentVMStarted = true + dvResourceVirtualEnvironmentVMTabletDevice = true + dvResourceVirtualEnvironmentVMVGAEnabled = true + dvResourceVirtualEnvironmentVMVGAMemory = 16 + dvResourceVirtualEnvironmentVMVGAType = "std" + dvResourceVirtualEnvironmentVMVMID = -1 - mkResourceVirtualEnvironmentVMACPI = "acpi" - mkResourceVirtualEnvironmentVMAgent = "agent" - mkResourceVirtualEnvironmentVMAgentEnabled = "enabled" - mkResourceVirtualEnvironmentVMAgentTrim = "trim" - mkResourceVirtualEnvironmentVMAgentType = "type" - mkResourceVirtualEnvironmentVMBIOS = "bios" - mkResourceVirtualEnvironmentVMCDROM = "cdrom" - mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled" - mkResourceVirtualEnvironmentVMCDROMFileID = "file_id" - mkResourceVirtualEnvironmentVMCloudInit = "cloud_init" - mkResourceVirtualEnvironmentVMCloudInitDNS = "dns" - mkResourceVirtualEnvironmentVMCloudInitDNSDomain = "domain" - mkResourceVirtualEnvironmentVMCloudInitDNSServer = "server" - mkResourceVirtualEnvironmentVMCloudInitIPConfig = "ip_config" - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4 = "ipv4" - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address = "address" - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway = "gateway" - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6 = "ipv6" - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Address = "address" - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Gateway = "gateway" - mkResourceVirtualEnvironmentVMCloudInitUserAccount = "user_account" - mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys = "keys" - mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword = "password" - mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername = "username" - mkResourceVirtualEnvironmentVMCloudInitUserDataFileID = "user_data_file_id" - mkResourceVirtualEnvironmentVMCPU = "cpu" - mkResourceVirtualEnvironmentVMCPUCores = "cores" - mkResourceVirtualEnvironmentVMCPUFlags = "flags" - mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged" - mkResourceVirtualEnvironmentVMCPUSockets = "sockets" - mkResourceVirtualEnvironmentVMCPUType = "type" - mkResourceVirtualEnvironmentVMCPUUnits = "units" - mkResourceVirtualEnvironmentVMDescription = "description" - mkResourceVirtualEnvironmentVMDisk = "disk" - mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id" - mkResourceVirtualEnvironmentVMDiskFileFormat = "file_format" - mkResourceVirtualEnvironmentVMDiskFileID = "file_id" - mkResourceVirtualEnvironmentVMDiskSize = "size" - mkResourceVirtualEnvironmentVMDiskSpeed = "speed" - mkResourceVirtualEnvironmentVMDiskSpeedRead = "read" - mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable = "read_burstable" - mkResourceVirtualEnvironmentVMDiskSpeedWrite = "write" - mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = "write_burstable" - mkResourceVirtualEnvironmentVMIPv4Addresses = "ipv4_addresses" - mkResourceVirtualEnvironmentVMIPv6Addresses = "ipv6_addresses" - mkResourceVirtualEnvironmentVMKeyboardLayout = "keyboard_layout" - mkResourceVirtualEnvironmentVMMACAddresses = "mac_addresses" - mkResourceVirtualEnvironmentVMMemory = "memory" - mkResourceVirtualEnvironmentVMMemoryDedicated = "dedicated" - mkResourceVirtualEnvironmentVMMemoryFloating = "floating" - mkResourceVirtualEnvironmentVMMemoryShared = "shared" - mkResourceVirtualEnvironmentVMName = "name" - mkResourceVirtualEnvironmentVMNetworkDevice = "network_device" - mkResourceVirtualEnvironmentVMNetworkDeviceBridge = "bridge" - mkResourceVirtualEnvironmentVMNetworkDeviceEnabled = "enabled" - mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress = "mac_address" - mkResourceVirtualEnvironmentVMNetworkDeviceModel = "model" - mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit = "rate_limit" - mkResourceVirtualEnvironmentVMNetworkDeviceVLANID = "vlan_id" - mkResourceVirtualEnvironmentVMNetworkInterfaceNames = "network_interface_names" - mkResourceVirtualEnvironmentVMNodeName = "node_name" - mkResourceVirtualEnvironmentVMOSType = "os_type" - mkResourceVirtualEnvironmentVMPoolID = "pool_id" - mkResourceVirtualEnvironmentVMStarted = "started" - mkResourceVirtualEnvironmentVMTabletDevice = "tablet_device" - mkResourceVirtualEnvironmentVMVGA = "vga" - mkResourceVirtualEnvironmentVMVGAEnabled = "enabled" - mkResourceVirtualEnvironmentVMVGAMemory = "memory" - mkResourceVirtualEnvironmentVMVGAType = "type" - mkResourceVirtualEnvironmentVMVMID = "vm_id" + mkResourceVirtualEnvironmentVMACPI = "acpi" + mkResourceVirtualEnvironmentVMAgent = "agent" + mkResourceVirtualEnvironmentVMAgentEnabled = "enabled" + mkResourceVirtualEnvironmentVMAgentTrim = "trim" + mkResourceVirtualEnvironmentVMAgentType = "type" + mkResourceVirtualEnvironmentVMBIOS = "bios" + mkResourceVirtualEnvironmentVMCDROM = "cdrom" + mkResourceVirtualEnvironmentVMCDROMEnabled = "enabled" + mkResourceVirtualEnvironmentVMCDROMFileID = "file_id" + mkResourceVirtualEnvironmentVMCPU = "cpu" + mkResourceVirtualEnvironmentVMCPUArchitecture = "architecture" + mkResourceVirtualEnvironmentVMCPUCores = "cores" + mkResourceVirtualEnvironmentVMCPUFlags = "flags" + mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged" + mkResourceVirtualEnvironmentVMCPUSockets = "sockets" + mkResourceVirtualEnvironmentVMCPUType = "type" + mkResourceVirtualEnvironmentVMCPUUnits = "units" + mkResourceVirtualEnvironmentVMDescription = "description" + mkResourceVirtualEnvironmentVMDisk = "disk" + mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id" + mkResourceVirtualEnvironmentVMDiskFileFormat = "file_format" + mkResourceVirtualEnvironmentVMDiskFileID = "file_id" + mkResourceVirtualEnvironmentVMDiskSize = "size" + mkResourceVirtualEnvironmentVMDiskSpeed = "speed" + mkResourceVirtualEnvironmentVMDiskSpeedRead = "read" + mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable = "read_burstable" + mkResourceVirtualEnvironmentVMDiskSpeedWrite = "write" + mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable = "write_burstable" + mkResourceVirtualEnvironmentVMInitialization = "initialization" + mkResourceVirtualEnvironmentVMInitializationDNS = "dns" + mkResourceVirtualEnvironmentVMInitializationDNSDomain = "domain" + mkResourceVirtualEnvironmentVMInitializationDNSServer = "server" + mkResourceVirtualEnvironmentVMInitializationIPConfig = "ip_config" + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4 = "ipv4" + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address = "address" + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway = "gateway" + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6 = "ipv6" + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address = "address" + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway = "gateway" + mkResourceVirtualEnvironmentVMInitializationUserAccount = "user_account" + mkResourceVirtualEnvironmentVMInitializationUserAccountKeys = "keys" + mkResourceVirtualEnvironmentVMInitializationUserAccountPassword = "password" + mkResourceVirtualEnvironmentVMInitializationUserAccountUsername = "username" + mkResourceVirtualEnvironmentVMInitializationUserDataFileID = "user_data_file_id" + mkResourceVirtualEnvironmentVMIPv4Addresses = "ipv4_addresses" + mkResourceVirtualEnvironmentVMIPv6Addresses = "ipv6_addresses" + mkResourceVirtualEnvironmentVMKeyboardLayout = "keyboard_layout" + mkResourceVirtualEnvironmentVMMACAddresses = "mac_addresses" + mkResourceVirtualEnvironmentVMMemory = "memory" + mkResourceVirtualEnvironmentVMMemoryDedicated = "dedicated" + mkResourceVirtualEnvironmentVMMemoryFloating = "floating" + mkResourceVirtualEnvironmentVMMemoryShared = "shared" + mkResourceVirtualEnvironmentVMName = "name" + mkResourceVirtualEnvironmentVMNetworkDevice = "network_device" + mkResourceVirtualEnvironmentVMNetworkDeviceBridge = "bridge" + mkResourceVirtualEnvironmentVMNetworkDeviceEnabled = "enabled" + mkResourceVirtualEnvironmentVMNetworkDeviceMACAddress = "mac_address" + mkResourceVirtualEnvironmentVMNetworkDeviceModel = "model" + mkResourceVirtualEnvironmentVMNetworkDeviceRateLimit = "rate_limit" + mkResourceVirtualEnvironmentVMNetworkDeviceVLANID = "vlan_id" + mkResourceVirtualEnvironmentVMNetworkInterfaceNames = "network_interface_names" + mkResourceVirtualEnvironmentVMNodeName = "node_name" + mkResourceVirtualEnvironmentVMOperatingSystem = "operating_system" + mkResourceVirtualEnvironmentVMOperatingSystemType = "type" + mkResourceVirtualEnvironmentVMPoolID = "pool_id" + mkResourceVirtualEnvironmentVMStarted = "started" + mkResourceVirtualEnvironmentVMTabletDevice = "tablet_device" + mkResourceVirtualEnvironmentVMVGA = "vga" + mkResourceVirtualEnvironmentVMVGAEnabled = "enabled" + mkResourceVirtualEnvironmentVMVGAMemory = "memory" + mkResourceVirtualEnvironmentVMVGAType = "type" + mkResourceVirtualEnvironmentVMVMID = "vm_id" ) func resourceVirtualEnvironmentVM() *schema.Resource { @@ -226,159 +233,6 @@ func resourceVirtualEnvironmentVM() *schema.Resource { MaxItems: 1, MinItems: 0, }, - mkResourceVirtualEnvironmentVMCloudInit: &schema.Schema{ - Type: schema.TypeList, - Description: "The cloud-init configuration", - Optional: true, - DefaultFunc: func() (interface{}, error) { - return []interface{}{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - mkResourceVirtualEnvironmentVMCloudInitDNS: { - Type: schema.TypeList, - Description: "The DNS configuration", - Optional: true, - DefaultFunc: func() (interface{}, error) { - return []interface{}{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - mkResourceVirtualEnvironmentVMCloudInitDNSDomain: { - Type: schema.TypeString, - Description: "The DNS search domain", - Optional: true, - Default: dvResourceVirtualEnvironmentVMCloudInitDNSDomain, - }, - mkResourceVirtualEnvironmentVMCloudInitDNSServer: { - Type: schema.TypeString, - Description: "The DNS server", - Optional: true, - Default: dvResourceVirtualEnvironmentVMCloudInitDNSServer, - }, - }, - }, - MaxItems: 1, - MinItems: 0, - }, - mkResourceVirtualEnvironmentVMCloudInitIPConfig: { - Type: schema.TypeList, - Description: "The IP configuration", - Optional: true, - DefaultFunc: func() (interface{}, error) { - return []interface{}{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4: { - Type: schema.TypeList, - Description: "The IPv4 configuration", - Optional: true, - DefaultFunc: func() (interface{}, error) { - return []interface{}{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address: { - Type: schema.TypeString, - Description: "The IPv4 address", - Optional: true, - Default: "", - }, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway: { - Type: schema.TypeString, - Description: "The IPv4 gateway", - Optional: true, - Default: "", - }, - }, - }, - MaxItems: 1, - MinItems: 0, - }, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6: { - Type: schema.TypeList, - Description: "The IPv6 configuration", - Optional: true, - DefaultFunc: func() (interface{}, error) { - return []interface{}{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Address: { - Type: schema.TypeString, - Description: "The IPv6 address", - Optional: true, - Default: "", - }, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Gateway: { - Type: schema.TypeString, - Description: "The IPv6 gateway", - Optional: true, - Default: "", - }, - }, - }, - MaxItems: 1, - MinItems: 0, - }, - }, - }, - MaxItems: 8, - MinItems: 0, - }, - mkResourceVirtualEnvironmentVMCloudInitUserAccount: { - Type: schema.TypeList, - Description: "The user account configuration", - Required: true, - ForceNew: true, - DefaultFunc: func() (interface{}, error) { - return []interface{}{}, nil - }, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys: { - Type: schema.TypeList, - Description: "The SSH keys", - Required: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword: { - Type: schema.TypeString, - Description: "The SSH password", - Optional: true, - ForceNew: true, - Sensitive: true, - Default: dvResourceVirtualEnvironmentVMCloudInitUserAccountPassword, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.ReplaceAll(old, "*", "") == "" - }, - }, - mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername: { - Type: schema.TypeString, - Description: "The SSH username", - Required: true, - ForceNew: true, - }, - }, - }, - MaxItems: 1, - MinItems: 0, - }, - mkResourceVirtualEnvironmentVMCloudInitUserDataFileID: { - Type: schema.TypeString, - Description: "The ID of a file containing custom user data", - Optional: true, - ForceNew: true, - Default: dvResourceVirtualEnvironmentVMCloudInitUserDataFileID, - ValidateFunc: getFileIDValidator(), - }, - }, - }, - MaxItems: 1, - MinItems: 0, - }, mkResourceVirtualEnvironmentVMCPU: &schema.Schema{ Type: schema.TypeList, Description: "The CPU allocation", @@ -387,6 +241,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { defaultList := make([]interface{}, 1) defaultMap := map[string]interface{}{} + defaultMap[mkResourceVirtualEnvironmentVMCPUArchitecture] = dvResourceVirtualEnvironmentVMCPUArchitecture defaultMap[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores defaultMap[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{} defaultMap[mkResourceVirtualEnvironmentVMCPUHotplugged] = dvResourceVirtualEnvironmentVMCPUHotplugged @@ -400,6 +255,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource { }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMCPUArchitecture: { + Type: schema.TypeString, + Description: "The CPU architecture", + Optional: true, + Default: dvResourceVirtualEnvironmentVMCPUArchitecture, + ValidateFunc: resourceVirtualEnvironmentVMGetCPUArchitectureValidator(), + }, mkResourceVirtualEnvironmentVMCPUCores: { Type: schema.TypeInt, Description: "The number of CPU cores", @@ -559,6 +421,159 @@ func resourceVirtualEnvironmentVM() *schema.Resource { MaxItems: 14, MinItems: 0, }, + mkResourceVirtualEnvironmentVMInitialization: &schema.Schema{ + Type: schema.TypeList, + Description: "The cloud-init configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMInitializationDNS: { + Type: schema.TypeList, + Description: "The DNS configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMInitializationDNSDomain: { + Type: schema.TypeString, + Description: "The DNS search domain", + Optional: true, + Default: dvResourceVirtualEnvironmentVMInitializationDNSDomain, + }, + mkResourceVirtualEnvironmentVMInitializationDNSServer: { + Type: schema.TypeString, + Description: "The DNS server", + Optional: true, + Default: dvResourceVirtualEnvironmentVMInitializationDNSServer, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentVMInitializationIPConfig: { + Type: schema.TypeList, + Description: "The IP configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4: { + Type: schema.TypeList, + Description: "The IPv4 configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address: { + Type: schema.TypeString, + Description: "The IPv4 address", + Optional: true, + Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address, + }, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway: { + Type: schema.TypeString, + Description: "The IPv4 gateway", + Optional: true, + Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6: { + Type: schema.TypeList, + Description: "The IPv6 configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address: { + Type: schema.TypeString, + Description: "The IPv6 address", + Optional: true, + Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address, + }, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway: { + Type: schema.TypeString, + Description: "The IPv6 gateway", + Optional: true, + Default: dvResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + }, + }, + MaxItems: 8, + MinItems: 0, + }, + mkResourceVirtualEnvironmentVMInitializationUserAccount: { + Type: schema.TypeList, + Description: "The user account configuration", + Optional: true, + ForceNew: true, + DefaultFunc: func() (interface{}, error) { + return []interface{}{}, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMInitializationUserAccountKeys: { + Type: schema.TypeList, + Description: "The SSH keys", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + mkResourceVirtualEnvironmentVMInitializationUserAccountPassword: { + Type: schema.TypeString, + Description: "The SSH password", + Optional: true, + ForceNew: true, + Sensitive: true, + Default: dvResourceVirtualEnvironmentVMInitializationUserAccountPassword, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.ReplaceAll(old, "*", "") == "" + }, + }, + mkResourceVirtualEnvironmentVMInitializationUserAccountUsername: { + Type: schema.TypeString, + Description: "The SSH username", + Optional: true, + ForceNew: true, + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, + mkResourceVirtualEnvironmentVMInitializationUserDataFileID: { + Type: schema.TypeString, + Description: "The ID of a file containing custom user data", + Optional: true, + ForceNew: true, + Default: dvResourceVirtualEnvironmentVMInitializationUserDataFileID, + ValidateFunc: getFileIDValidator(), + }, + }, + }, + MaxItems: 1, + MinItems: 0, + }, mkResourceVirtualEnvironmentVMIPv4Addresses: { Type: schema.TypeList, Description: "The IPv4 addresses published by the QEMU agent", @@ -707,12 +722,33 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Required: true, ForceNew: true, }, - mkResourceVirtualEnvironmentVMOSType: { - Type: schema.TypeString, - Description: "The OS type", - Optional: true, - Default: dvResourceVirtualEnvironmentVMOSType, - ValidateFunc: getOSTypeValidator(), + mkResourceVirtualEnvironmentVMOperatingSystem: &schema.Schema{ + Type: schema.TypeList, + Description: "The operating system configuration", + Optional: true, + DefaultFunc: func() (interface{}, error) { + defaultList := make([]interface{}, 1) + defaultMap := map[string]interface{}{} + + defaultMap[mkResourceVirtualEnvironmentVMOperatingSystemType] = dvResourceVirtualEnvironmentVMOperatingSystemType + + defaultList[0] = defaultMap + + return defaultList, nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentVMOperatingSystemType: { + Type: schema.TypeString, + Description: "The type", + Optional: true, + Default: dvResourceVirtualEnvironmentVMOperatingSystemType, + ValidateFunc: resourceVirtualEnvironmentVMGetOperatingSystemTypeValidator(), + }, + }, + }, + MaxItems: 1, + MinItems: 0, }, mkResourceVirtualEnvironmentVMPoolID: { Type: schema.TypeString, @@ -829,23 +865,13 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e cdromFileID = "cdrom" } - cloudInitConfig, err := resourceVirtualEnvironmentVMGetCloudConfig(d, m) - - if err != nil { - return err - } - - if cloudInitConfig != nil { - cdromEnabled = true - cdromFileID = "local-lvm:cloudinit" - } - cpuBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMCPU}, 0, true) if err != nil { return err } + cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentVMCPUArchitecture].(string) cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int) cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{}) cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int) @@ -860,6 +886,17 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e return err } + initializationConfig, err := resourceVirtualEnvironmentVMGetCloudInitConfig(d, m) + + if err != nil { + return err + } + + if initializationConfig != nil { + cdromEnabled = true + cdromFileID = "local-lvm:cloudinit" + } + keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string) memoryBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMMemory}, 0, true) @@ -880,7 +917,15 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e } nodeName := d.Get(mkResourceVirtualEnvironmentVMNodeName).(string) - osType := d.Get(mkResourceVirtualEnvironmentVMOSType).(string) + + operatingSystem, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMOperatingSystem}, 0, true) + + if err != nil { + return err + } + + operatingSystemType := operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType].(string) + poolID := d.Get(mkResourceVirtualEnvironmentVMPoolID).(string) started := proxmox.CustomBool(d.Get(mkResourceVirtualEnvironmentVMStarted).(bool)) tabletDevice := proxmox.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool)) @@ -953,7 +998,8 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e BIOS: &bios, BootDisk: &bootDisk, BootOrder: &bootOrder, - CloudInitConfig: cloudInitConfig, + CloudInitConfig: initializationConfig, + CPUArchitecture: &cpuArchitecture, CPUCores: &cpuCores, CPUEmulation: &proxmox.CustomCPUEmulation{ Flags: &cpuFlagsConverted, @@ -966,7 +1012,7 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e IDEDevices: ideDevices, KeyboardLayout: &keyboardLayout, NetworkDevices: networkDeviceObjects, - OSType: &osType, + OSType: &operatingSystemType, PoolID: &poolID, SCSIDevices: diskDeviceObjects, SCSIHardware: &scsiHardware, @@ -1153,7 +1199,7 @@ func resourceVirtualEnvironmentVMCreateStart(d *schema.ResourceData, m interface return err } - err = veClient.WaitForState(nodeName, vmID, "running", 120, 5) + err = veClient.WaitForVMState(nodeName, vmID, "running", 120, 5) if err != nil { return err @@ -1162,76 +1208,76 @@ func resourceVirtualEnvironmentVMCreateStart(d *schema.ResourceData, m interface return resourceVirtualEnvironmentVMRead(d, m) } -func resourceVirtualEnvironmentVMGetCloudConfig(d *schema.ResourceData, m interface{}) (*proxmox.CustomCloudInitConfig, error) { - var cloudInitConfig *proxmox.CustomCloudInitConfig +func resourceVirtualEnvironmentVMGetCloudInitConfig(d *schema.ResourceData, m interface{}) (*proxmox.CustomCloudInitConfig, error) { + var initializationConfig *proxmox.CustomCloudInitConfig - cloudInit := d.Get(mkResourceVirtualEnvironmentVMCloudInit).([]interface{}) + initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{}) - if len(cloudInit) > 0 { - cloudInitBlock := cloudInit[0].(map[string]interface{}) - cloudInitConfig = &proxmox.CustomCloudInitConfig{} - cloudInitDNS := cloudInitBlock[mkResourceVirtualEnvironmentVMCloudInitDNS].([]interface{}) + if len(initialization) > 0 { + initializationBlock := initialization[0].(map[string]interface{}) + initializationConfig = &proxmox.CustomCloudInitConfig{} + initializationDNS := initializationBlock[mkResourceVirtualEnvironmentVMInitializationDNS].([]interface{}) - if len(cloudInitDNS) > 0 { - cloudInitDNSBlock := cloudInitDNS[0].(map[string]interface{}) - domain := cloudInitDNSBlock[mkResourceVirtualEnvironmentVMCloudInitDNSDomain].(string) + if len(initializationDNS) > 0 { + initializationDNSBlock := initializationDNS[0].(map[string]interface{}) + domain := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSDomain].(string) if domain != "" { - cloudInitConfig.SearchDomain = &domain + initializationConfig.SearchDomain = &domain } - server := cloudInitDNSBlock[mkResourceVirtualEnvironmentVMCloudInitDNSServer].(string) + server := initializationDNSBlock[mkResourceVirtualEnvironmentVMInitializationDNSServer].(string) if server != "" { - cloudInitConfig.Nameserver = &server + initializationConfig.Nameserver = &server } } - cloudInitIPConfig := cloudInitBlock[mkResourceVirtualEnvironmentVMCloudInitIPConfig].([]interface{}) - cloudInitConfig.IPConfig = make([]proxmox.CustomCloudInitIPConfig, len(cloudInitIPConfig)) + initializationIPConfig := initializationBlock[mkResourceVirtualEnvironmentVMInitializationIPConfig].([]interface{}) + initializationConfig.IPConfig = make([]proxmox.CustomCloudInitIPConfig, len(initializationIPConfig)) - for i, c := range cloudInitIPConfig { + for i, c := range initializationIPConfig { configBlock := c.(map[string]interface{}) - ipv4 := configBlock[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4].([]interface{}) + ipv4 := configBlock[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4].([]interface{}) if len(ipv4) > 0 { ipv4Block := ipv4[0].(map[string]interface{}) - ipv4Address := ipv4Block[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address].(string) + ipv4Address := ipv4Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address].(string) if ipv4Address != "" { - cloudInitConfig.IPConfig[i].IPv4 = &ipv4Address + initializationConfig.IPConfig[i].IPv4 = &ipv4Address } - ipv4Gateway := ipv4Block[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway].(string) + ipv4Gateway := ipv4Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway].(string) if ipv4Gateway != "" { - cloudInitConfig.IPConfig[i].GatewayIPv4 = &ipv4Gateway + initializationConfig.IPConfig[i].GatewayIPv4 = &ipv4Gateway } } - ipv6 := configBlock[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6].([]interface{}) + ipv6 := configBlock[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6].([]interface{}) if len(ipv6) > 0 { ipv6Block := ipv6[0].(map[string]interface{}) - ipv6Address := ipv6Block[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Address].(string) + ipv6Address := ipv6Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address].(string) if ipv6Address != "" { - cloudInitConfig.IPConfig[i].IPv6 = &ipv6Address + initializationConfig.IPConfig[i].IPv6 = &ipv6Address } - ipv6Gateway := ipv6Block[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Gateway].(string) + ipv6Gateway := ipv6Block[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway].(string) if ipv6Gateway != "" { - cloudInitConfig.IPConfig[i].GatewayIPv6 = &ipv6Gateway + initializationConfig.IPConfig[i].GatewayIPv6 = &ipv6Gateway } } } - cloudInitUserAccount := cloudInitBlock[mkResourceVirtualEnvironmentVMCloudInitUserAccount].([]interface{}) + initializationUserAccount := initializationBlock[mkResourceVirtualEnvironmentVMInitializationUserAccount].([]interface{}) - if len(cloudInitUserAccount) > 0 { - cloudInitUserAccountBlock := cloudInitUserAccount[0].(map[string]interface{}) - keys := cloudInitUserAccountBlock[mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys].([]interface{}) + if len(initializationUserAccount) > 0 { + initializationUserAccountBlock := initializationUserAccount[0].(map[string]interface{}) + keys := initializationUserAccountBlock[mkResourceVirtualEnvironmentVMInitializationUserAccountKeys].([]interface{}) if len(keys) > 0 { sshKeys := make(proxmox.CustomCloudInitSSHKeys, len(keys)) @@ -1240,30 +1286,37 @@ func resourceVirtualEnvironmentVMGetCloudConfig(d *schema.ResourceData, m interf sshKeys[i] = k.(string) } - cloudInitConfig.SSHKeys = &sshKeys + initializationConfig.SSHKeys = &sshKeys } - password := cloudInitUserAccountBlock[mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword].(string) + password := initializationUserAccountBlock[mkResourceVirtualEnvironmentVMInitializationUserAccountPassword].(string) if password != "" { - cloudInitConfig.Password = &password + initializationConfig.Password = &password } - username := cloudInitUserAccountBlock[mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername].(string) + username := initializationUserAccountBlock[mkResourceVirtualEnvironmentVMInitializationUserAccountUsername].(string) - cloudInitConfig.Username = &username + initializationConfig.Username = &username } - cloudInitUserDataFileID := cloudInitBlock[mkResourceVirtualEnvironmentVMCloudInitUserDataFileID].(string) + initializationUserDataFileID := initializationBlock[mkResourceVirtualEnvironmentVMInitializationUserDataFileID].(string) - if cloudInitUserDataFileID != "" { - cloudInitConfig.Files = &proxmox.CustomCloudInitFiles{ - UserVolume: &cloudInitUserDataFileID, + if initializationUserDataFileID != "" { + initializationConfig.Files = &proxmox.CustomCloudInitFiles{ + UserVolume: &initializationUserDataFileID, } } } - return cloudInitConfig, nil + return initializationConfig, nil +} + +func resourceVirtualEnvironmentVMGetCPUArchitectureValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "aarch64", + "x86_64", + }, false) } func resourceVirtualEnvironmentVMGetDiskDeviceObjects(d *schema.ResourceData, m interface{}) (proxmox.CustomStorageDevices, error) { @@ -1363,6 +1416,23 @@ func resourceVirtualEnvironmentVMGetNetworkDeviceObjects(d *schema.ResourceData, return networkDeviceObjects, nil } +func resourceVirtualEnvironmentVMGetOperatingSystemTypeValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "l24", + "l26", + "other", + "solaris", + "w2k", + "w2k3", + "w2k8", + "win7", + "win8", + "win10", + "wvista", + "wxp", + }, false) +} + func resourceVirtualEnvironmentVMGetVGADeviceObject(d *schema.ResourceData, m interface{}) (*proxmox.CustomVGADevice, error) { resource := resourceVirtualEnvironmentVM() @@ -1497,7 +1567,7 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err if strings.Contains(vmConfig.IDEDevice2.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID)) { d.Set(mkResourceVirtualEnvironmentVMCDROM, []interface{}{}) } else { - d.Set(mkResourceVirtualEnvironmentVMCloudInit, []interface{}{}) + d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{}) cdrom := make([]interface{}, 1) cdromBlock := map[string]interface{}{} @@ -1511,145 +1581,23 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err } } else { d.Set(mkResourceVirtualEnvironmentVMCDROM, []interface{}{}) - d.Set(mkResourceVirtualEnvironmentVMCloudInit, []interface{}{}) + d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{}) } } else { d.Set(mkResourceVirtualEnvironmentVMCDROM, []interface{}{}) - d.Set(mkResourceVirtualEnvironmentVMCloudInit, []interface{}{}) - } - - // Compare the cloud-init configuration to the one stored in the state. - cloudInit := map[string]interface{}{} - - if vmConfig.CloudInitDNSDomain != nil || vmConfig.CloudInitDNSServer != nil { - cloudInitDNS := map[string]interface{}{} - - if vmConfig.CloudInitDNSDomain != nil { - cloudInitDNS[mkResourceVirtualEnvironmentVMCloudInitDNSDomain] = *vmConfig.CloudInitDNSDomain - } else { - cloudInitDNS[mkResourceVirtualEnvironmentVMCloudInitDNSDomain] = "" - } - - if vmConfig.CloudInitDNSServer != nil { - cloudInitDNS[mkResourceVirtualEnvironmentVMCloudInitDNSServer] = *vmConfig.CloudInitDNSServer - } else { - cloudInitDNS[mkResourceVirtualEnvironmentVMCloudInitDNSServer] = "" - } - - cloudInit[mkResourceVirtualEnvironmentVMCloudInitDNS] = []interface{}{cloudInitDNS} - } - - ipConfigLast := -1 - ipConfigObjects := []*proxmox.CustomCloudInitIPConfig{ - vmConfig.IPConfig0, - vmConfig.IPConfig1, - vmConfig.IPConfig2, - vmConfig.IPConfig3, - vmConfig.IPConfig4, - vmConfig.IPConfig5, - vmConfig.IPConfig6, - vmConfig.IPConfig7, - } - ipConfigList := make([]interface{}, len(ipConfigObjects)) - - for ipConfigIndex, ipConfig := range ipConfigObjects { - ipConfigItem := map[string]interface{}{} - - if ipConfig != nil { - ipConfigLast = ipConfigIndex - - if ipConfig.GatewayIPv4 != nil || ipConfig.IPv4 != nil { - ipv4 := map[string]interface{}{} - - if ipConfig.IPv4 != nil { - ipv4[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address] = *ipConfig.IPv4 - } else { - ipv4[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address] = "" - } - - if ipConfig.GatewayIPv4 != nil { - ipv4[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway] = *ipConfig.GatewayIPv4 - } else { - ipv4[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway] = "" - } - - ipConfigItem[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4] = []interface{}{ipv4} - } else { - ipConfigItem[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4] = []interface{}{} - } - - if ipConfig.GatewayIPv6 != nil || ipConfig.IPv6 != nil { - ipv6 := map[string]interface{}{} - - if ipConfig.IPv4 != nil { - ipv6[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address] = *ipConfig.IPv6 - } else { - ipv6[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address] = "" - } - - if ipConfig.GatewayIPv4 != nil { - ipv6[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway] = *ipConfig.GatewayIPv6 - } else { - ipv6[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway] = "" - } - - ipConfigItem[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6] = []interface{}{ipv6} - } else { - ipConfigItem[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6] = []interface{}{} - } - } else { - ipConfigItem[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4] = []interface{}{} - ipConfigItem[mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6] = []interface{}{} - } - - ipConfigList[ipConfigIndex] = ipConfigItem - } - - cloudInit[mkResourceVirtualEnvironmentVMCloudInitIPConfig] = ipConfigList[:ipConfigLast+1] - - if vmConfig.CloudInitPassword != nil || vmConfig.CloudInitSSHKeys != nil || vmConfig.CloudInitUsername != nil { - cloudInitUserAccount := map[string]interface{}{} - - if vmConfig.CloudInitSSHKeys != nil { - cloudInitUserAccount[mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys] = []string(*vmConfig.CloudInitSSHKeys) - } else { - cloudInitUserAccount[mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys] = []string{} - } - - if vmConfig.CloudInitPassword != nil { - cloudInitUserAccount[mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword] = *vmConfig.CloudInitPassword - } else { - cloudInitUserAccount[mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword] = "" - } - - if vmConfig.CloudInitUsername != nil { - cloudInitUserAccount[mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername] = *vmConfig.CloudInitUsername - } else { - cloudInitUserAccount[mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername] = "" - } - - cloudInit[mkResourceVirtualEnvironmentVMCloudInitUserAccount] = []interface{}{cloudInitUserAccount} - } - - if vmConfig.CloudInitFiles != nil { - if vmConfig.CloudInitFiles.UserVolume != nil { - cloudInit[mkResourceVirtualEnvironmentVMCloudInitUserDataFileID] = *vmConfig.CloudInitFiles.UserVolume - } else { - cloudInit[mkResourceVirtualEnvironmentVMCloudInitUserDataFileID] = "" - } - } else { - cloudInit[mkResourceVirtualEnvironmentVMCloudInitUserDataFileID] = "" - } - - if len(cloudInit) > 0 { - d.Set(mkResourceVirtualEnvironmentVMCloudInit, []interface{}{cloudInit}) - } else { - d.Set(mkResourceVirtualEnvironmentVMCloudInit, []interface{}{}) + d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{}) } // Compare the CPU configuration to the one stored in the state. cpu := map[string]interface{}{} + if vmConfig.CPUArchitecture != nil { + cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] = *vmConfig.CPUArchitecture + } else { + // Default value of "arch" is "" according to the API documentation. + cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] = "" + } + if vmConfig.CPUCores != nil { cpu[mkResourceVirtualEnvironmentVMCPUCores] = *vmConfig.CPUCores } else { @@ -1701,6 +1649,7 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err currentCPU := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{}) if len(currentCPU) > 0 || + cpu[mkResourceVirtualEnvironmentVMCPUArchitecture] != dvResourceVirtualEnvironmentVMCPUArchitecture || cpu[mkResourceVirtualEnvironmentVMCPUCores] != dvResourceVirtualEnvironmentVMCPUCores || len(cpu[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})) > 0 || cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] != dvResourceVirtualEnvironmentVMCPUHotplugged || @@ -1823,6 +1772,135 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err d.Set(mkResourceVirtualEnvironmentVMDisk, diskList) } + // Compare the cloud-init configuration to the one stored in the state. + initialization := map[string]interface{}{} + + if vmConfig.CloudInitDNSDomain != nil || vmConfig.CloudInitDNSServer != nil { + initializationDNS := map[string]interface{}{} + + if vmConfig.CloudInitDNSDomain != nil { + initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSDomain] = *vmConfig.CloudInitDNSDomain + } else { + initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSDomain] = "" + } + + if vmConfig.CloudInitDNSServer != nil { + initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServer] = *vmConfig.CloudInitDNSServer + } else { + initializationDNS[mkResourceVirtualEnvironmentVMInitializationDNSServer] = "" + } + + initialization[mkResourceVirtualEnvironmentVMInitializationDNS] = []interface{}{initializationDNS} + } + + ipConfigLast := -1 + ipConfigObjects := []*proxmox.CustomCloudInitIPConfig{ + vmConfig.IPConfig0, + vmConfig.IPConfig1, + vmConfig.IPConfig2, + vmConfig.IPConfig3, + vmConfig.IPConfig4, + vmConfig.IPConfig5, + vmConfig.IPConfig6, + vmConfig.IPConfig7, + } + ipConfigList := make([]interface{}, len(ipConfigObjects)) + + for ipConfigIndex, ipConfig := range ipConfigObjects { + ipConfigItem := map[string]interface{}{} + + if ipConfig != nil { + ipConfigLast = ipConfigIndex + + if ipConfig.GatewayIPv4 != nil || ipConfig.IPv4 != nil { + ipv4 := map[string]interface{}{} + + if ipConfig.IPv4 != nil { + ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address] = *ipConfig.IPv4 + } else { + ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address] = "" + } + + if ipConfig.GatewayIPv4 != nil { + ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway] = *ipConfig.GatewayIPv4 + } else { + ipv4[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway] = "" + } + + ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4] = []interface{}{ipv4} + } else { + ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4] = []interface{}{} + } + + if ipConfig.GatewayIPv6 != nil || ipConfig.IPv6 != nil { + ipv6 := map[string]interface{}{} + + if ipConfig.IPv4 != nil { + ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address] = *ipConfig.IPv6 + } else { + ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address] = "" + } + + if ipConfig.GatewayIPv4 != nil { + ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway] = *ipConfig.GatewayIPv6 + } else { + ipv6[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway] = "" + } + + ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6] = []interface{}{ipv6} + } else { + ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6] = []interface{}{} + } + } else { + ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4] = []interface{}{} + ipConfigItem[mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6] = []interface{}{} + } + + ipConfigList[ipConfigIndex] = ipConfigItem + } + + initialization[mkResourceVirtualEnvironmentVMInitializationIPConfig] = ipConfigList[:ipConfigLast+1] + + if vmConfig.CloudInitPassword != nil || vmConfig.CloudInitSSHKeys != nil || vmConfig.CloudInitUsername != nil { + initializationUserAccount := map[string]interface{}{} + + if vmConfig.CloudInitSSHKeys != nil { + initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountKeys] = []string(*vmConfig.CloudInitSSHKeys) + } else { + initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountKeys] = []string{} + } + + if vmConfig.CloudInitPassword != nil { + initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountPassword] = *vmConfig.CloudInitPassword + } else { + initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountPassword] = "" + } + + if vmConfig.CloudInitUsername != nil { + initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountUsername] = *vmConfig.CloudInitUsername + } else { + initializationUserAccount[mkResourceVirtualEnvironmentVMInitializationUserAccountUsername] = "" + } + + initialization[mkResourceVirtualEnvironmentVMInitializationUserAccount] = []interface{}{initializationUserAccount} + } + + if vmConfig.CloudInitFiles != nil { + if vmConfig.CloudInitFiles.UserVolume != nil { + initialization[mkResourceVirtualEnvironmentVMInitializationUserDataFileID] = *vmConfig.CloudInitFiles.UserVolume + } else { + initialization[mkResourceVirtualEnvironmentVMInitializationUserDataFileID] = "" + } + } else { + initialization[mkResourceVirtualEnvironmentVMInitializationUserDataFileID] = "" + } + + if len(initialization) > 0 { + d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{initialization}) + } else { + d.Set(mkResourceVirtualEnvironmentVMInitialization, []interface{}{}) + } + // Compare the memory configuration to the one stored in the state. memory := map[string]interface{}{} @@ -1925,13 +2003,25 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err d.Set(mkResourceVirtualEnvironmentVMNetworkDevice, networkDeviceList[:networkDeviceLast+1]) } - // Compare the OS type and pool ID to the values stored in the state. + // Compare the operating system configuration to the one stored in the state. + operatingSystem := map[string]interface{}{} + if vmConfig.OSType != nil { - d.Set(mkResourceVirtualEnvironmentVMOSType, *vmConfig.OSType) + operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType] = *vmConfig.OSType } else { - d.Set(mkResourceVirtualEnvironmentVMOSType, "") + operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType] = "" } + currentOperatingSystem := d.Get(mkResourceVirtualEnvironmentVMOperatingSystem).([]interface{}) + + if len(currentOperatingSystem) > 0 || + operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType] != dvResourceVirtualEnvironmentVMOperatingSystemType { + d.Set(mkResourceVirtualEnvironmentVMOperatingSystem, []interface{}{operatingSystem}) + } else { + d.Set(mkResourceVirtualEnvironmentVMOperatingSystem, []interface{}{}) + } + + // Compare the pool ID to the values stored in the state. if vmConfig.PoolID != nil { d.Set(mkResourceVirtualEnvironmentVMPoolID, *vmConfig.PoolID) } @@ -1993,7 +2083,7 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err networkInterfaceNames := []interface{}{} if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled { - networkInterfaces, err := veClient.WaitForNetworkInterfacesFromAgent(nodeName, vmID, 1800, 5) + networkInterfaces, err := veClient.WaitForNetworkInterfacesFromVMAgent(nodeName, vmID, 1800, 5) if err == nil && networkInterfaces.Result != nil { ipv4Addresses = make([]interface{}, len(*networkInterfaces.Result)) @@ -2077,7 +2167,6 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string) keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string) name := d.Get(mkResourceVirtualEnvironmentVMName).(string) - osType := d.Get(mkResourceVirtualEnvironmentVMOSType).(string) tabletDevice := proxmox.CustomBool(d.Get(mkResourceVirtualEnvironmentVMTabletDevice).(bool)) body.ACPI = &acpi @@ -2093,13 +2182,12 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e body.Name = &name } - body.OSType = &osType body.TabletDeviceEnabled = &tabletDevice if d.HasChange(mkResourceVirtualEnvironmentVMACPI) || d.HasChange(mkResourceVirtualEnvironmentVMBIOS) || d.HasChange(mkResourceVirtualEnvironmentVMKeyboardLayout) || - d.HasChange(mkResourceVirtualEnvironmentVMOSType) || + d.HasChange(mkResourceVirtualEnvironmentVMOperatingSystemType) || d.HasChange(mkResourceVirtualEnvironmentVMTabletDevice) { rebootRequired = true } @@ -2149,35 +2237,6 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e } } - // Prepare the new cloud-init configuration. - if d.HasChange(mkResourceVirtualEnvironmentVMCloudInit) { - cloudInitConfig, err := resourceVirtualEnvironmentVMGetCloudConfig(d, m) - - if err != nil { - return err - } - - body.CloudInitConfig = cloudInitConfig - - if body.CloudInitConfig != nil { - cdromMedia := "cdrom" - - body.IDEDevices[2] = proxmox.CustomStorageDevice{ - Enabled: true, - FileVolume: "local-lvm:cloudinit", - Media: &cdromMedia, - } - - if vmConfig.IDEDevice2 != nil { - if strings.Contains(vmConfig.IDEDevice2.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID)) { - body.IDEDevices[2].Enabled = false - } - } - } - - rebootRequired = true - } - // Prepare the new CPU configuration. if d.HasChange(mkResourceVirtualEnvironmentVMCPU) { cpuBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMCPU}, 0, true) @@ -2186,6 +2245,7 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e return err } + cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentVMCPUArchitecture].(string) cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int) cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{}) cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int) @@ -2193,6 +2253,7 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string) cpuUnits := cpuBlock[mkResourceVirtualEnvironmentVMCPUUnits].(int) + body.CPUArchitecture = &cpuArchitecture body.CPUCores = &cpuCores body.CPUSockets = &cpuSockets body.CPUUnits = &cpuUnits @@ -2259,6 +2320,35 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e rebootRequired = true } + // Prepare the new cloud-init configuration. + if d.HasChange(mkResourceVirtualEnvironmentVMInitialization) { + initializationConfig, err := resourceVirtualEnvironmentVMGetCloudInitConfig(d, m) + + if err != nil { + return err + } + + body.CloudInitConfig = initializationConfig + + if body.CloudInitConfig != nil { + cdromMedia := "cdrom" + + body.IDEDevices[2] = proxmox.CustomStorageDevice{ + Enabled: true, + FileVolume: "local-lvm:cloudinit", + Media: &cdromMedia, + } + + if vmConfig.IDEDevice2 != nil { + if strings.Contains(vmConfig.IDEDevice2.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID)) { + body.IDEDevices[2].Enabled = false + } + } + } + + rebootRequired = true + } + // Prepare the new memory configuration. if d.HasChange(mkResourceVirtualEnvironmentVMMemory) { memoryBlock, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMMemory}, 0, true) @@ -2297,6 +2387,21 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e rebootRequired = true } + // Prepare the new operating system configuration. + if d.HasChange(mkResourceVirtualEnvironmentVMOperatingSystem) { + operatingSystem, err := getSchemaBlock(resource, d, m, []string{mkResourceVirtualEnvironmentVMOperatingSystem}, 0, true) + + if err != nil { + return err + } + + operatingSystemType := operatingSystem[mkResourceVirtualEnvironmentVMOperatingSystemType].(string) + + body.OSType = &operatingSystemType + + rebootRequired = true + } + // Prepare the new VGA configuration. if d.HasChange(mkResourceVirtualEnvironmentVMVGA) { body.VGADevice, err = resourceVirtualEnvironmentVMGetVGADeviceObject(d, m) @@ -2328,7 +2433,7 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e return err } - err = veClient.WaitForState(nodeName, vmID, "running", 120, 5) + err = veClient.WaitForVMState(nodeName, vmID, "running", 120, 5) if err != nil { return err @@ -2346,7 +2451,7 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e return err } - err = veClient.WaitForState(nodeName, vmID, "stopped", 30, 5) + err = veClient.WaitForVMState(nodeName, vmID, "stopped", 30, 5) if err != nil { return err @@ -2370,7 +2475,7 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e // Wait for the agent to unpublish the network interfaces, if it's enabled. if vmConfig.Agent != nil && vmConfig.Agent.Enabled != nil && *vmConfig.Agent.Enabled { - err = veClient.WaitForNoNetworkInterfacesFromAgent(nodeName, vmID, 300, 5) + err = veClient.WaitForNoNetworkInterfacesFromVMAgent(nodeName, vmID, 300, 5) if err != nil { return err @@ -2409,7 +2514,7 @@ func resourceVirtualEnvironmentVMDelete(d *schema.ResourceData, m interface{}) e return err } - err = veClient.WaitForState(nodeName, vmID, "stopped", 30, 5) + err = veClient.WaitForVMState(nodeName, vmID, "stopped", 30, 5) if err != nil { return err @@ -2428,7 +2533,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.WaitForState(nodeName, vmID, "", 30, 2) + err = veClient.WaitForVMState(nodeName, vmID, "", 30, 2) if err == nil { return fmt.Errorf("Failed to delete VM \"%d\"", vmID) diff --git a/proxmoxtf/resource_virtual_environment_vm_test.go b/proxmoxtf/resource_virtual_environment_vm_test.go index acff0c41..46228676 100644 --- a/proxmoxtf/resource_virtual_environment_vm_test.go +++ b/proxmoxtf/resource_virtual_environment_vm_test.go @@ -31,7 +31,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMACPI, mkResourceVirtualEnvironmentVMBIOS, mkResourceVirtualEnvironmentVMCDROM, - mkResourceVirtualEnvironmentVMCloudInit, + mkResourceVirtualEnvironmentVMInitialization, mkResourceVirtualEnvironmentVMCPU, mkResourceVirtualEnvironmentVMDescription, mkResourceVirtualEnvironmentVMDisk, @@ -39,7 +39,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMMemory, mkResourceVirtualEnvironmentVMName, mkResourceVirtualEnvironmentVMNetworkDevice, - mkResourceVirtualEnvironmentVMOSType, + mkResourceVirtualEnvironmentVMOperatingSystem, mkResourceVirtualEnvironmentVMPoolID, mkResourceVirtualEnvironmentVMStarted, mkResourceVirtualEnvironmentVMTabletDevice, @@ -58,7 +58,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMAgent, mkResourceVirtualEnvironmentVMBIOS, mkResourceVirtualEnvironmentVMCDROM, - mkResourceVirtualEnvironmentVMCloudInit, + mkResourceVirtualEnvironmentVMInitialization, mkResourceVirtualEnvironmentVMCPU, mkResourceVirtualEnvironmentVMDescription, mkResourceVirtualEnvironmentVMDisk, @@ -70,7 +70,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMNetworkDevice, mkResourceVirtualEnvironmentVMMACAddresses, mkResourceVirtualEnvironmentVMNetworkInterfaceNames, - mkResourceVirtualEnvironmentVMOSType, + mkResourceVirtualEnvironmentVMOperatingSystem, mkResourceVirtualEnvironmentVMPoolID, mkResourceVirtualEnvironmentVMStarted, mkResourceVirtualEnvironmentVMTabletDevice, @@ -92,7 +92,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { schema.TypeList, schema.TypeList, schema.TypeList, - schema.TypeString, + schema.TypeList, schema.TypeString, schema.TypeBool, schema.TypeBool, @@ -132,111 +132,10 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { schema.TypeString, }) - cloudInitSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMCloudInit) - - testRequiredArguments(t, cloudInitSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitUserAccount, - }) - - testOptionalArguments(t, cloudInitSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitDNS, - mkResourceVirtualEnvironmentVMCloudInitIPConfig, - }) - - testSchemaValueTypes(t, cloudInitSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitDNS, - mkResourceVirtualEnvironmentVMCloudInitIPConfig, - mkResourceVirtualEnvironmentVMCloudInitUserAccount, - }, []schema.ValueType{ - schema.TypeList, - schema.TypeList, - schema.TypeList, - }) - - cloudInitDNSSchema := testNestedSchemaExistence(t, cloudInitSchema, mkResourceVirtualEnvironmentVMCloudInitDNS) - - testOptionalArguments(t, cloudInitDNSSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitDNSDomain, - mkResourceVirtualEnvironmentVMCloudInitDNSServer, - }) - - testSchemaValueTypes(t, cloudInitDNSSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitDNSDomain, - mkResourceVirtualEnvironmentVMCloudInitDNSServer, - }, []schema.ValueType{ - schema.TypeString, - schema.TypeString, - }) - - cloudInitIPConfigSchema := testNestedSchemaExistence(t, cloudInitSchema, mkResourceVirtualEnvironmentVMCloudInitIPConfig) - - testOptionalArguments(t, cloudInitIPConfigSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6, - }) - - testSchemaValueTypes(t, cloudInitIPConfigSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6, - }, []schema.ValueType{ - schema.TypeList, - schema.TypeList, - }) - - cloudInitIPConfigIPv4Schema := testNestedSchemaExistence(t, cloudInitIPConfigSchema, mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4) - - testOptionalArguments(t, cloudInitIPConfigIPv4Schema, []string{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway, - }) - - testSchemaValueTypes(t, cloudInitIPConfigIPv4Schema, []string{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Address, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv4Gateway, - }, []schema.ValueType{ - schema.TypeString, - schema.TypeString, - }) - - cloudInitIPConfigIPv6Schema := testNestedSchemaExistence(t, cloudInitIPConfigSchema, mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6) - - testOptionalArguments(t, cloudInitIPConfigIPv6Schema, []string{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Address, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Gateway, - }) - - testSchemaValueTypes(t, cloudInitIPConfigIPv6Schema, []string{ - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Address, - mkResourceVirtualEnvironmentVMCloudInitIPConfigIPv6Gateway, - }, []schema.ValueType{ - schema.TypeString, - schema.TypeString, - }) - - cloudInitUserAccountSchema := testNestedSchemaExistence(t, cloudInitSchema, mkResourceVirtualEnvironmentVMCloudInitUserAccount) - - testRequiredArguments(t, cloudInitUserAccountSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys, - mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername, - }) - - testOptionalArguments(t, cloudInitUserAccountSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword, - }) - - testSchemaValueTypes(t, cloudInitUserAccountSchema, []string{ - mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys, - mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword, - mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername, - }, []schema.ValueType{ - schema.TypeList, - schema.TypeString, - schema.TypeString, - }) - cpuSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMCPU) testOptionalArguments(t, cpuSchema, []string{ + mkResourceVirtualEnvironmentVMCPUArchitecture, mkResourceVirtualEnvironmentVMCPUCores, mkResourceVirtualEnvironmentVMCPUFlags, mkResourceVirtualEnvironmentVMCPUHotplugged, @@ -246,6 +145,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { }) testSchemaValueTypes(t, cpuSchema, []string{ + mkResourceVirtualEnvironmentVMCPUArchitecture, mkResourceVirtualEnvironmentVMCPUCores, mkResourceVirtualEnvironmentVMCPUFlags, mkResourceVirtualEnvironmentVMCPUHotplugged, @@ -253,6 +153,7 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { mkResourceVirtualEnvironmentVMCPUType, mkResourceVirtualEnvironmentVMCPUUnits, }, []schema.ValueType{ + schema.TypeString, schema.TypeInt, schema.TypeList, schema.TypeInt, @@ -303,6 +204,102 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { schema.TypeInt, }) + initializationSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMInitialization) + + testOptionalArguments(t, initializationSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationDNS, + mkResourceVirtualEnvironmentVMInitializationIPConfig, + mkResourceVirtualEnvironmentVMInitializationUserAccount, + }) + + testSchemaValueTypes(t, initializationSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationDNS, + mkResourceVirtualEnvironmentVMInitializationIPConfig, + mkResourceVirtualEnvironmentVMInitializationUserAccount, + }, []schema.ValueType{ + schema.TypeList, + schema.TypeList, + schema.TypeList, + }) + + initializationDNSSchema := testNestedSchemaExistence(t, initializationSchema, mkResourceVirtualEnvironmentVMInitializationDNS) + + testOptionalArguments(t, initializationDNSSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationDNSDomain, + mkResourceVirtualEnvironmentVMInitializationDNSServer, + }) + + testSchemaValueTypes(t, initializationDNSSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationDNSDomain, + mkResourceVirtualEnvironmentVMInitializationDNSServer, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeString, + }) + + initializationIPConfigSchema := testNestedSchemaExistence(t, initializationSchema, mkResourceVirtualEnvironmentVMInitializationIPConfig) + + testOptionalArguments(t, initializationIPConfigSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6, + }) + + testSchemaValueTypes(t, initializationIPConfigSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6, + }, []schema.ValueType{ + schema.TypeList, + schema.TypeList, + }) + + initializationIPConfigIPv4Schema := testNestedSchemaExistence(t, initializationIPConfigSchema, mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4) + + testOptionalArguments(t, initializationIPConfigIPv4Schema, []string{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway, + }) + + testSchemaValueTypes(t, initializationIPConfigIPv4Schema, []string{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Address, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv4Gateway, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeString, + }) + + initializationIPConfigIPv6Schema := testNestedSchemaExistence(t, initializationIPConfigSchema, mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6) + + testOptionalArguments(t, initializationIPConfigIPv6Schema, []string{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway, + }) + + testSchemaValueTypes(t, initializationIPConfigIPv6Schema, []string{ + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Address, + mkResourceVirtualEnvironmentVMInitializationIPConfigIPv6Gateway, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeString, + }) + + initializationUserAccountSchema := testNestedSchemaExistence(t, initializationSchema, mkResourceVirtualEnvironmentVMInitializationUserAccount) + + testOptionalArguments(t, initializationUserAccountSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationUserAccountKeys, + mkResourceVirtualEnvironmentVMInitializationUserAccountPassword, + mkResourceVirtualEnvironmentVMInitializationUserAccountUsername, + }) + + testSchemaValueTypes(t, initializationUserAccountSchema, []string{ + mkResourceVirtualEnvironmentVMInitializationUserAccountKeys, + mkResourceVirtualEnvironmentVMInitializationUserAccountPassword, + mkResourceVirtualEnvironmentVMInitializationUserAccountUsername, + }, []schema.ValueType{ + schema.TypeList, + schema.TypeString, + schema.TypeString, + }) + memorySchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMMemory) testOptionalArguments(t, memorySchema, []string{ @@ -348,6 +345,18 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) { schema.TypeInt, }) + operatingSystemSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMOperatingSystem) + + testOptionalArguments(t, operatingSystemSchema, []string{ + mkResourceVirtualEnvironmentVMOperatingSystemType, + }) + + testSchemaValueTypes(t, operatingSystemSchema, []string{ + mkResourceVirtualEnvironmentVMOperatingSystemType, + }, []schema.ValueType{ + schema.TypeString, + }) + vgaSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMVGA) testOptionalArguments(t, vgaSchema, []string{ diff --git a/proxmoxtf/utils.go b/proxmoxtf/utils.go index ebf4e762..6aaacdbc 100644 --- a/proxmoxtf/utils.go +++ b/proxmoxtf/utils.go @@ -225,23 +225,6 @@ func getNetworkDeviceModelValidator() schema.SchemaValidateFunc { return validation.StringInSlice([]string{"e1000", "rtl8139", "virtio", "vmxnet3"}, false) } -func getOSTypeValidator() schema.SchemaValidateFunc { - return validation.StringInSlice([]string{ - "l24", - "l26", - "other", - "solaris", - "w2k", - "w2k3", - "w2k8", - "win7", - "win8", - "win10", - "wvista", - "wxp", - }, false) -} - func getQEMUAgentTypeValidator() schema.SchemaValidateFunc { return validation.StringInSlice([]string{"isa", "virtio"}, false) }