0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00
terraform-provider-proxmox/proxmoxtf/resource/container.go
Pavel Boldyrev 343e8045c1
feat(vm): add network device resources (#376)
* wip

* experimenting with terraform plugin framework

* cleaning up poc and adding tests

* adding read / update / delete

* update bridge_vlan_aware and MTU

* add ipv6 and simplify IP support

* fix provider's schema

* add docs

* run linter from cmdline

* disable TF acceptance tests

* add VLAN

* update docs

* add examole

* cleanup
2023-06-23 22:47:25 +00:00

2415 lines
86 KiB
Go

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package resource
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/bpg/terraform-provider-proxmox/internal/types"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/containers"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf"
)
const (
dvResourceVirtualEnvironmentContainerCloneDatastoreID = ""
dvResourceVirtualEnvironmentContainerCloneNodeName = ""
dvResourceVirtualEnvironmentContainerConsoleEnabled = true
dvResourceVirtualEnvironmentContainerConsoleMode = "tty"
dvResourceVirtualEnvironmentContainerConsoleTTYCount = 2
dvResourceVirtualEnvironmentContainerInitializationDNSDomain = ""
dvResourceVirtualEnvironmentContainerInitializationDNSServer = ""
dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address = ""
dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway = ""
dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address = ""
dvResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway = ""
dvResourceVirtualEnvironmentContainerInitializationHostname = ""
dvResourceVirtualEnvironmentContainerInitializationUserAccountPassword = ""
dvResourceVirtualEnvironmentContainerCPUArchitecture = "amd64"
dvResourceVirtualEnvironmentContainerCPUCores = 1
dvResourceVirtualEnvironmentContainerCPUUnits = 1024
dvResourceVirtualEnvironmentContainerDescription = ""
dvResourceVirtualEnvironmentContainerDiskDatastoreID = "local"
dvResourceVirtualEnvironmentContainerDiskSize = 4
dvResourceVirtualEnvironmentContainerFeaturesNesting = false
dvResourceVirtualEnvironmentContainerMemoryDedicated = 512
dvResourceVirtualEnvironmentContainerMemorySwap = 0
dvResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "vmbr0"
dvResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = true
dvResourceVirtualEnvironmentContainerNetworkInterfaceFirewall = false
dvResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress = ""
dvResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit = 0
dvResourceVirtualEnvironmentContainerNetworkInterfaceVLANID = 0
dvResourceVirtualEnvironmentContainerOperatingSystemType = "unmanaged"
dvResourceVirtualEnvironmentContainerPoolID = ""
dvResourceVirtualEnvironmentContainerStarted = true
dvResourceVirtualEnvironmentContainerTemplate = false
dvResourceVirtualEnvironmentContainerUnprivileged = false
dvResourceVirtualEnvironmentContainerVMID = -1
maxResourceVirtualEnvironmentContainerNetworkInterfaces = 8
mkResourceVirtualEnvironmentContainerClone = "clone"
mkResourceVirtualEnvironmentContainerCloneDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentContainerCloneNodeName = "node_name"
mkResourceVirtualEnvironmentContainerCloneVMID = "vm_id"
mkResourceVirtualEnvironmentContainerConsole = "console"
mkResourceVirtualEnvironmentContainerConsoleEnabled = "enabled"
mkResourceVirtualEnvironmentContainerConsoleMode = "type"
mkResourceVirtualEnvironmentContainerConsoleTTYCount = "tty_count"
mkResourceVirtualEnvironmentContainerCPU = "cpu"
mkResourceVirtualEnvironmentContainerCPUArchitecture = "architecture"
mkResourceVirtualEnvironmentContainerCPUCores = "cores"
mkResourceVirtualEnvironmentContainerCPUUnits = "units"
mkResourceVirtualEnvironmentContainerDescription = "description"
mkResourceVirtualEnvironmentContainerDisk = "disk"
mkResourceVirtualEnvironmentContainerDiskDatastoreID = "datastore_id"
mkResourceVirtualEnvironmentContainerDiskSize = "size"
mkResourceVirtualEnvironmentContainerFeatures = "features"
mkResourceVirtualEnvironmentContainerFeaturesNesting = "nesting"
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"
mkResourceVirtualEnvironmentContainerMemory = "memory"
mkResourceVirtualEnvironmentContainerMemoryDedicated = "dedicated"
mkResourceVirtualEnvironmentContainerMemorySwap = "swap"
mkResourceVirtualEnvironmentContainerNetworkInterface = "network_interface"
mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge = "bridge"
mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled = "enabled"
mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall = "firewall"
mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress = "mac_address"
mkResourceVirtualEnvironmentContainerNetworkInterfaceName = "name"
mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit = "rate_limit"
mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID = "vlan_id"
mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU = "mtu"
mkResourceVirtualEnvironmentContainerNodeName = "node_name"
mkResourceVirtualEnvironmentContainerOperatingSystem = "operating_system"
mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID = "template_file_id"
mkResourceVirtualEnvironmentContainerOperatingSystemType = "type"
mkResourceVirtualEnvironmentContainerPoolID = "pool_id"
mkResourceVirtualEnvironmentContainerStarted = "started"
mkResourceVirtualEnvironmentContainerTags = "tags"
mkResourceVirtualEnvironmentContainerTemplate = "template"
mkResourceVirtualEnvironmentContainerUnprivileged = "unprivileged"
mkResourceVirtualEnvironmentContainerVMID = "vm_id"
)
// Container returns a resource that manages a container.
func Container() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentContainerClone: {
Type: schema.TypeList,
Description: "The cloning configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentContainerCloneDatastoreID: {
Type: schema.TypeString,
Description: "The ID of the target datastore",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerCloneDatastoreID,
},
mkResourceVirtualEnvironmentContainerCloneNodeName: {
Type: schema.TypeString,
Description: "The name of the source node",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerCloneNodeName,
},
mkResourceVirtualEnvironmentContainerCloneVMID: {
Type: schema.TypeInt,
Description: "The ID of the source container",
Required: true,
ForceNew: true,
ValidateDiagFunc: getVMIDValidator(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerConsole: {
Type: schema.TypeList,
Description: "The console configuration",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentContainerConsoleEnabled: dvResourceVirtualEnvironmentContainerConsoleEnabled,
mkResourceVirtualEnvironmentContainerConsoleMode: dvResourceVirtualEnvironmentContainerConsoleMode,
mkResourceVirtualEnvironmentContainerConsoleTTYCount: dvResourceVirtualEnvironmentContainerConsoleTTYCount,
},
}, 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,
ValidateDiagFunc: containerGetConsoleModeValidator(),
},
mkResourceVirtualEnvironmentContainerConsoleTTYCount: {
Type: schema.TypeInt,
Description: "The number of available TTY",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerConsoleTTYCount,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 6)),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerCPU: {
Type: schema.TypeList,
Description: "The CPU allocation",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentContainerCPUArchitecture: dvResourceVirtualEnvironmentContainerCPUArchitecture,
mkResourceVirtualEnvironmentContainerCPUCores: dvResourceVirtualEnvironmentContainerCPUCores,
mkResourceVirtualEnvironmentContainerCPUUnits: dvResourceVirtualEnvironmentContainerCPUUnits,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentContainerCPUArchitecture: {
Type: schema.TypeString,
Description: "The CPU architecture",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerCPUArchitecture,
ValidateDiagFunc: containerGetCPUArchitectureValidator(),
},
mkResourceVirtualEnvironmentContainerCPUCores: {
Type: schema.TypeInt,
Description: "The number of CPU cores",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerCPUCores,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 128)),
},
mkResourceVirtualEnvironmentContainerCPUUnits: {
Type: schema.TypeInt,
Description: "The CPU units",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerCPUUnits,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(0, 500000),
),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerDescription: {
Type: schema.TypeString,
Description: "The description",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerDescription,
},
mkResourceVirtualEnvironmentContainerDisk: {
Type: schema.TypeList,
Description: "The disks",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentContainerDiskDatastoreID: dvResourceVirtualEnvironmentContainerDiskDatastoreID,
mkResourceVirtualEnvironmentContainerDiskSize: dvResourceVirtualEnvironmentContainerDiskSize,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentContainerDiskDatastoreID: {
Type: schema.TypeString,
Description: "The datastore id",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerDiskDatastoreID,
},
mkResourceVirtualEnvironmentContainerDiskSize: {
Type: schema.TypeInt,
Description: "The rootfs size in gigabytes",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerDiskSize,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerFeatures: {
Type: schema.TypeList,
Description: "Features",
Optional: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentContainerFeaturesNesting: dvResourceVirtualEnvironmentContainerFeaturesNesting,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentContainerFeaturesNesting: {
Type: schema.TypeBool,
Description: "Whether the container runs as nested",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerFeaturesNesting,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerInitialization: {
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 len(old) > 0 &&
strings.ReplaceAll(old, "*", "") == ""
},
},
},
},
MaxItems: 1,
MinItems: 0,
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerMemory: {
Type: schema.TypeList,
Description: "The memory allocation",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkResourceVirtualEnvironmentContainerMemoryDedicated: dvResourceVirtualEnvironmentContainerMemoryDedicated,
mkResourceVirtualEnvironmentContainerMemorySwap: dvResourceVirtualEnvironmentContainerMemorySwap,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentContainerMemoryDedicated: {
Type: schema.TypeInt,
Description: "The dedicated memory in megabytes",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerMemoryDedicated,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(16, 268435456),
),
},
mkResourceVirtualEnvironmentContainerMemorySwap: {
Type: schema.TypeInt,
Description: "The swap size in megabytes",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerMemorySwap,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(0, 268435456),
),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerNetworkInterface: {
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,
},
mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall: {
Type: schema.TypeBool,
Description: "Whether this interface's firewall rules should be used.",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceFirewall,
},
mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress: {
Type: schema.TypeString,
Description: "The MAC address",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return new == ""
},
ValidateDiagFunc: 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,
},
mkResourceVirtualEnvironmentVMNetworkDeviceMTU: {
Type: schema.TypeInt,
Description: "Maximum transmission unit (MTU)",
Optional: true,
Default: dvResourceVirtualEnvironmentVMNetworkDeviceMTU,
},
},
},
MaxItems: maxResourceVirtualEnvironmentContainerNetworkInterfaces,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerNodeName: {
Type: schema.TypeString,
Description: "The node name",
Required: true,
ForceNew: true,
},
mkResourceVirtualEnvironmentContainerOperatingSystem: {
Type: schema.TypeList,
Description: "The operating system configuration",
Optional: true,
ForceNew: 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,
ValidateDiagFunc: getFileIDValidator(),
},
mkResourceVirtualEnvironmentContainerOperatingSystemType: {
Type: schema.TypeString,
Description: "The type",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerOperatingSystemType,
ValidateDiagFunc: containerGetOperatingSystemTypeValidator(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkResourceVirtualEnvironmentContainerPoolID: {
Type: schema.TypeString,
Description: "The ID of the pool to assign the container to",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerPoolID,
},
mkResourceVirtualEnvironmentContainerStarted: {
Type: schema.TypeBool,
Description: "Whether to start the container",
Optional: true,
Default: dvResourceVirtualEnvironmentContainerStarted,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return d.Get(mkResourceVirtualEnvironmentContainerTemplate).(bool)
},
},
mkResourceVirtualEnvironmentContainerTags: {
Type: schema.TypeList,
Description: "Tags of the container. This is only meta information.",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
DiffSuppressFunc: suppressIfListsAreEqualIgnoringOrder,
DiffSuppressOnRefresh: true,
},
mkResourceVirtualEnvironmentContainerTemplate: {
Type: schema.TypeBool,
Description: "Whether to create a template",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerTemplate,
},
mkResourceVirtualEnvironmentContainerUnprivileged: {
Type: schema.TypeBool,
Description: "Whether the container runs as unprivileged on the host",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerUnprivileged,
},
mkResourceVirtualEnvironmentContainerVMID: {
Type: schema.TypeInt,
Description: "The VM identifier",
Optional: true,
ForceNew: true,
Default: dvResourceVirtualEnvironmentContainerVMID,
ValidateDiagFunc: getVMIDValidator(),
},
},
CreateContext: containerCreate,
ReadContext: containerRead,
UpdateContext: containerUpdate,
DeleteContext: containerDelete,
}
}
func containerCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
clone := d.Get(mkResourceVirtualEnvironmentContainerClone).([]interface{})
if len(clone) > 0 {
return containerCreateClone(ctx, d, m)
}
return containerCreateCustom(ctx, d, m)
}
func containerCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
clone := d.Get(mkResourceVirtualEnvironmentContainerClone).([]interface{})
cloneBlock := clone[0].(map[string]interface{})
cloneDatastoreID := cloneBlock[mkResourceVirtualEnvironmentContainerCloneDatastoreID].(string)
cloneNodeName := cloneBlock[mkResourceVirtualEnvironmentContainerCloneNodeName].(string)
cloneVMID := cloneBlock[mkResourceVirtualEnvironmentContainerCloneVMID].(int)
description := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string)
initialization := d.Get(mkResourceVirtualEnvironmentContainerInitialization).([]interface{})
initializationHostname := ""
if len(initialization) > 0 {
initializationBlock := initialization[0].(map[string]interface{})
initializationHostname = initializationBlock[mkResourceVirtualEnvironmentContainerInitializationHostname].(string)
}
nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string)
poolID := d.Get(mkResourceVirtualEnvironmentContainerPoolID).(string)
tags := d.Get(mkResourceVirtualEnvironmentContainerTags).([]interface{})
vmID := d.Get(mkResourceVirtualEnvironmentContainerVMID).(int)
if vmID == -1 {
vmIDNew, e := api.Cluster().GetVMID(ctx)
if e != nil {
return diag.FromErr(e)
}
vmID = *vmIDNew
}
fullCopy := types.CustomBool(true)
cloneBody := &containers.CloneRequestBody{
FullCopy: &fullCopy,
VMIDNew: vmID,
}
if cloneDatastoreID != "" {
cloneBody.TargetStorage = &cloneDatastoreID
}
if description != "" {
cloneBody.Description = &description
}
if initializationHostname != "" {
cloneBody.Hostname = &initializationHostname
}
if poolID != "" {
cloneBody.PoolID = &poolID
}
if cloneNodeName != "" && cloneNodeName != nodeName {
cloneBody.TargetNodeName = &nodeName
err = api.Node(cloneNodeName).Container(cloneVMID).CloneContainer(ctx, cloneBody)
} else {
err = api.Node(nodeName).Container(cloneVMID).CloneContainer(ctx, cloneBody)
}
if err != nil {
return diag.FromErr(err)
}
d.SetId(strconv.Itoa(vmID))
containerAPI := api.Node(nodeName).Container(vmID)
// Wait for the container to be created and its configuration lock to be released.
err = containerAPI.WaitForContainerLock(ctx, 600, 5, true)
if err != nil {
return diag.FromErr(err)
}
// Now that the virtual machine has been cloned, we need to perform some modifications.
updateBody := &containers.UpdateRequestBody{}
console := d.Get(mkResourceVirtualEnvironmentContainerConsole).([]interface{})
if len(console) > 0 {
consoleBlock := console[0].(map[string]interface{})
consoleEnabled := types.CustomBool(
consoleBlock[mkResourceVirtualEnvironmentContainerConsoleEnabled].(bool),
)
consoleMode := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleMode].(string)
consoleTTYCount := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleTTYCount].(int)
updateBody.ConsoleEnabled = &consoleEnabled
updateBody.ConsoleMode = &consoleMode
updateBody.TTY = &consoleTTYCount
}
cpu := d.Get(mkResourceVirtualEnvironmentContainerCPU).([]interface{})
if len(cpu) > 0 {
cpuBlock := cpu[0].(map[string]interface{})
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentContainerCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentContainerCPUCores].(int)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentContainerCPUUnits].(int)
updateBody.CPUArchitecture = &cpuArchitecture
updateBody.CPUCores = &cpuCores
updateBody.CPUUnits = &cpuUnits
}
var initializationIPConfigIPv4Address []string
var initializationIPConfigIPv4Gateway []string
var initializationIPConfigIPv6Address []string
var initializationIPConfigIPv6Gateway []string
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)
updateBody.DNSDomain = &initializationDNSDomain
updateBody.DNSServer = &initializationDNSServer
}
initializationHostname := initializationBlock[mkResourceVirtualEnvironmentContainerInitializationHostname].(string)
if initializationHostname != dvResourceVirtualEnvironmentContainerInitializationHostname {
updateBody.Hostname = &initializationHostname
}
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{})
if len(keys) > 0 {
initializationUserAccountKeys := make(
containers.CustomSSHKeys,
len(keys),
)
for ki, kv := range keys {
initializationUserAccountKeys[ki] = kv.(string)
}
updateBody.SSHKeys = &initializationUserAccountKeys
} else {
updateBody.Delete = append(updateBody.Delete, "ssh-public-keys")
}
initializationUserAccountPassword := initializationUserAccountBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword].(string)
if initializationUserAccountPassword != dvResourceVirtualEnvironmentContainerInitializationUserAccountPassword {
updateBody.Password = &initializationUserAccountPassword
} else {
updateBody.Delete = append(updateBody.Delete, "password")
}
}
}
memory := d.Get(mkResourceVirtualEnvironmentContainerMemory).([]interface{})
if len(memory) > 0 {
memoryBlock := memory[0].(map[string]interface{})
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentContainerMemoryDedicated].(int)
memorySwap := memoryBlock[mkResourceVirtualEnvironmentContainerMemorySwap].(int)
updateBody.DedicatedMemory = &memoryDedicated
updateBody.Swap = &memorySwap
}
networkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{})
if len(networkInterface) == 0 {
networkInterface, err = containerGetExistingNetworkInterface(ctx, containerAPI)
if err != nil {
return diag.FromErr(err)
}
}
networkInterfaceArray := make(
containers.CustomNetworkInterfaceArray,
len(networkInterface),
)
for ni, nv := range networkInterface {
networkInterfaceMap := nv.(map[string]interface{})
networkInterfaceObject := containers.CustomNetworkInterface{}
bridge := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge].(string)
enabled := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled].(bool)
firewall := types.CustomBool(
networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall].(bool),
)
macAddress := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress].(string)
name := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceName].(string)
rateLimit := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit].(float64)
vlanID := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID].(int)
mtu, _ := networkInterfaceMap[mkResourceVirtualEnvironmentVMNetworkDeviceMTU].(int)
if bridge != "" {
networkInterfaceObject.Bridge = &bridge
}
networkInterfaceObject.Enabled = enabled
networkInterfaceObject.Firewall = &firewall
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
}
networkInterfaceObject.Name = name
if rateLimit != 0 {
networkInterfaceObject.RateLimit = &rateLimit
}
if vlanID != 0 {
networkInterfaceObject.Tag = &vlanID
}
if mtu != 0 {
networkInterfaceObject.MTU = &mtu
}
networkInterfaceArray[ni] = networkInterfaceObject
}
updateBody.NetworkInterfaces = networkInterfaceArray
for i := 0; i < len(updateBody.NetworkInterfaces); i++ {
if !updateBody.NetworkInterfaces[i].Enabled {
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
}
}
for i := len(updateBody.NetworkInterfaces); i < maxResourceVirtualEnvironmentContainerNetworkInterfaces; i++ {
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
}
operatingSystem := d.Get(mkResourceVirtualEnvironmentContainerOperatingSystem).([]interface{})
if len(operatingSystem) > 0 {
operatingSystemBlock := operatingSystem[0].(map[string]interface{})
operatingSystemTemplateFileID := operatingSystemBlock[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID].(string)
operatingSystemType := operatingSystemBlock[mkResourceVirtualEnvironmentContainerOperatingSystemType].(string)
updateBody.OSTemplateFileVolume = &operatingSystemTemplateFileID
updateBody.OSType = &operatingSystemType
}
if len(tags) > 0 {
tagString := containerGetTagsString(d)
updateBody.Tags = &tagString
}
template := types.CustomBool(d.Get(mkResourceVirtualEnvironmentContainerTemplate).(bool))
//nolint:gosimple
if template != dvResourceVirtualEnvironmentContainerTemplate {
updateBody.Template = &template
}
err = containerAPI.UpdateContainer(ctx, updateBody)
if err != nil {
return diag.FromErr(err)
}
// Wait for the container's lock to be released.
err = containerAPI.WaitForContainerLock(ctx, 600, 5, true)
if err != nil {
return diag.FromErr(err)
}
return containerCreateStart(ctx, d, m)
}
func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string)
resource := Container()
consoleBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerConsole},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
consoleEnabled := types.CustomBool(
consoleBlock[mkResourceVirtualEnvironmentContainerConsoleEnabled].(bool),
)
consoleMode := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleMode].(string)
consoleTTYCount := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleTTYCount].(int)
cpuBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerCPU},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentContainerCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentContainerCPUCores].(int)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentContainerCPUUnits].(int)
description := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string)
diskBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerDisk},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
diskDatastoreID := diskBlock[mkResourceVirtualEnvironmentContainerDiskDatastoreID].(string)
var rootFS *containers.CustomRootFS
diskSize := diskBlock[mkResourceVirtualEnvironmentContainerDiskSize].(int)
if diskSize != dvResourceVirtualEnvironmentContainerDiskSize && diskDatastoreID != "" {
// This is a special case where the rootfs size is set to a non-default value at creation time.
// see https://pve.proxmox.com/pve-docs/chapter-pct.html#_storage_backed_mount_points
rootFS = &containers.CustomRootFS{
Volume: fmt.Sprintf("%s:%d", diskDatastoreID, diskSize),
}
}
featuresBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerFeatures},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
nesting := types.CustomBool(featuresBlock[mkResourceVirtualEnvironmentContainerFeaturesNesting].(bool))
features := containers.CustomFeatures{
Nesting: &nesting,
}
initialization := d.Get(mkResourceVirtualEnvironmentContainerInitialization).([]interface{})
initializationDNSDomain := dvResourceVirtualEnvironmentContainerInitializationDNSDomain
initializationDNSServer := dvResourceVirtualEnvironmentContainerInitializationDNSServer
initializationHostname := dvResourceVirtualEnvironmentContainerInitializationHostname
var initializationIPConfigIPv4Address []string
var initializationIPConfigIPv4Gateway []string
var initializationIPConfigIPv6Address []string
var initializationIPConfigIPv6Gateway []string
initializationUserAccountKeys := containers.CustomSSHKeys{}
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(
containers.CustomSSHKeys,
len(keys),
)
for ki, kv := range keys {
initializationUserAccountKeys[ki] = kv.(string)
}
initializationUserAccountPassword = initializationUserAccountBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccountPassword].(string)
}
}
memoryBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerMemory},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentContainerMemoryDedicated].(int)
memorySwap := memoryBlock[mkResourceVirtualEnvironmentContainerMemorySwap].(int)
networkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{})
networkInterfaceArray := make(
containers.CustomNetworkInterfaceArray,
len(networkInterface),
)
for ni, nv := range networkInterface {
networkInterfaceMap := nv.(map[string]interface{})
networkInterfaceObject := containers.CustomNetworkInterface{}
bridge := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge].(string)
enabled := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled].(bool)
macAddress := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress].(string)
name := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceName].(string)
rateLimit := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit].(float64)
vlanID := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID].(int)
mtu := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU].(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
}
networkInterfaceObject.Name = name
if rateLimit != 0 {
networkInterfaceObject.RateLimit = &rateLimit
}
if vlanID != 0 {
networkInterfaceObject.Tag = &vlanID
}
if mtu != 0 {
networkInterfaceObject.MTU = &mtu
}
networkInterfaceArray[ni] = networkInterfaceObject
}
operatingSystem := d.Get(mkResourceVirtualEnvironmentContainerOperatingSystem).([]interface{})
if len(operatingSystem) == 0 {
return diag.Errorf(
"\"%s\": required field is not set",
mkResourceVirtualEnvironmentContainerOperatingSystem,
)
}
operatingSystemBlock := operatingSystem[0].(map[string]interface{})
operatingSystemTemplateFileID := operatingSystemBlock[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID].(string)
operatingSystemType := operatingSystemBlock[mkResourceVirtualEnvironmentContainerOperatingSystemType].(string)
poolID := d.Get(mkResourceVirtualEnvironmentContainerPoolID).(string)
started := types.CustomBool(d.Get(mkResourceVirtualEnvironmentContainerStarted).(bool))
tags := d.Get(mkResourceVirtualEnvironmentContainerTags).([]interface{})
template := types.CustomBool(d.Get(mkResourceVirtualEnvironmentContainerTemplate).(bool))
unprivileged := types.CustomBool(d.Get(mkResourceVirtualEnvironmentContainerUnprivileged).(bool))
vmID := d.Get(mkResourceVirtualEnvironmentContainerVMID).(int)
if vmID == -1 {
vmIDNew, e := api.Cluster().GetVMID(ctx)
if e != nil {
return diag.FromErr(e)
}
vmID = *vmIDNew
}
// Attempt to create the resource using the retrieved values.
createBody := containers.CreateRequestBody{
ConsoleEnabled: &consoleEnabled,
ConsoleMode: &consoleMode,
CPUArchitecture: &cpuArchitecture,
CPUCores: &cpuCores,
CPUUnits: &cpuUnits,
DatastoreID: &diskDatastoreID,
DedicatedMemory: &memoryDedicated,
Features: &features,
NetworkInterfaces: networkInterfaceArray,
OSTemplateFileVolume: &operatingSystemTemplateFileID,
OSType: &operatingSystemType,
RootFS: rootFS,
StartOnBoot: &started,
Swap: &memorySwap,
Template: &template,
TTY: &consoleTTYCount,
Unprivileged: &unprivileged,
VMID: &vmID,
}
if description != "" {
createBody.Description = &description
}
if initializationDNSDomain != "" {
createBody.DNSDomain = &initializationDNSDomain
}
if initializationDNSServer != "" {
createBody.DNSServer = &initializationDNSServer
}
if initializationHostname != "" {
createBody.Hostname = &initializationHostname
}
if len(initializationUserAccountKeys) > 0 {
createBody.SSHKeys = &initializationUserAccountKeys
}
if initializationUserAccountPassword != "" {
createBody.Password = &initializationUserAccountPassword
}
if poolID != "" {
createBody.PoolID = &poolID
}
if len(tags) > 0 {
tagsString := containerGetTagsString(d)
createBody.Tags = &tagsString
}
err = api.Node(nodeName).Container(0).CreateContainer(ctx, &createBody)
if err != nil {
return diag.FromErr(err)
}
d.SetId(strconv.Itoa(vmID))
// Wait for the container's lock to be released.
err = api.Node(nodeName).Container(vmID).WaitForContainerLock(ctx, 600, 5, true)
if err != nil {
return diag.FromErr(err)
}
return containerCreateStart(ctx, d, m)
}
func containerCreateStart(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
started := d.Get(mkResourceVirtualEnvironmentContainerStarted).(bool)
template := d.Get(mkResourceVirtualEnvironmentContainerTemplate).(bool)
if !started || template {
return containerRead(ctx, d, m)
}
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string)
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
containerAPI := api.Node(nodeName).Container(vmID)
// Start the container and wait for it to reach a running state before continuing.
err = containerAPI.StartContainer(ctx)
if err != nil {
return diag.FromErr(err)
}
err = containerAPI.WaitForContainerState(ctx, "running", 120, 5)
if err != nil {
return diag.FromErr(err)
}
return containerRead(ctx, d, m)
}
func containerGetConsoleModeValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"console",
"shell",
"tty",
}, false))
}
func containerGetCPUArchitectureValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"amd64",
"arm64",
"armhf",
"i386",
}, false))
}
func containerGetExistingNetworkInterface(
ctx context.Context,
containerAPI *containers.Client,
) ([]interface{}, error) {
containerInfo, err := containerAPI.GetContainer(ctx)
if err != nil {
return []interface{}{}, err
}
var networkInterfaces []interface{}
networkInterfaceArray := []*containers.CustomNetworkInterface{
containerInfo.NetworkInterface0,
containerInfo.NetworkInterface1,
containerInfo.NetworkInterface2,
containerInfo.NetworkInterface3,
containerInfo.NetworkInterface4,
containerInfo.NetworkInterface5,
containerInfo.NetworkInterface6,
containerInfo.NetworkInterface7,
}
for _, nv := range networkInterfaceArray {
if nv == nil {
continue
}
networkInterface := map[string]interface{}{}
if nv.Bridge != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge] = *nv.Bridge
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge] = ""
}
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled] = true
if nv.Firewall != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall] = *nv.Firewall
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall] = false
}
if nv.MACAddress != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress] = *nv.MACAddress
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress] = ""
}
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceName] = nv.Name
if nv.RateLimit != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit] = *nv.RateLimit
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit] = float64(0)
}
if nv.Tag != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID] = *nv.Tag
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID] = 0
}
if nv.MTU != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU] = *nv.MTU
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU] = 0
}
networkInterfaces = append(networkInterfaces, networkInterface)
}
return networkInterfaces, nil
}
func containerGetOperatingSystemTypeValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"alpine",
"archlinux",
"centos",
"debian",
"fedora",
"gentoo",
"opensuse",
"ubuntu",
"unmanaged",
}, false))
}
func containerGetTagsString(d *schema.ResourceData) string {
var sanitizedTags []string
tags := d.Get(mkResourceVirtualEnvironmentContainerTags).([]interface{})
for i := 0; i < len(tags); i++ {
tag := strings.TrimSpace(tags[i].(string))
if len(tag) > 0 {
sanitizedTags = append(sanitizedTags, tag)
}
}
sort.Strings(sanitizedTags)
return strings.Join(sanitizedTags, ";")
}
func containerRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
config := m.(proxmoxtf.ProviderConfiguration)
api, e := config.GetClient()
if e != nil {
return diag.FromErr(e)
}
nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string)
vmID, e := strconv.Atoi(d.Id())
if e != nil {
return diag.FromErr(e)
}
containerAPI := api.Node(nodeName).Container(vmID)
// Retrieve the entire configuration in order to compare it to the state.
containerConfig, e := containerAPI.GetContainer(ctx)
if e != nil {
if strings.Contains(e.Error(), "HTTP 404") ||
(strings.Contains(e.Error(), "HTTP 500") && strings.Contains(e.Error(), "does not exist")) {
d.SetId("")
return nil
}
return diag.FromErr(e)
}
clone := d.Get(mkResourceVirtualEnvironmentContainerClone).([]interface{})
// Compare the primitive values to those stored in the state.
currentDescription := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string)
if len(clone) == 0 || currentDescription != dvResourceVirtualEnvironmentContainerDescription {
if containerConfig.Description != nil {
e = d.Set(
mkResourceVirtualEnvironmentContainerDescription,
strings.TrimSpace(*containerConfig.Description),
)
} else {
e = d.Set(mkResourceVirtualEnvironmentContainerDescription, "")
}
diags = append(diags, diag.FromErr(e)...)
}
// Compare the console configuration to the one stored in the state.
console := map[string]interface{}{}
if containerConfig.ConsoleEnabled != nil {
console[mkResourceVirtualEnvironmentContainerConsoleEnabled] = *containerConfig.ConsoleEnabled
} else {
// Default value of "console" is "1" according to the API documentation.
console[mkResourceVirtualEnvironmentContainerConsoleEnabled] = true
}
if containerConfig.ConsoleMode != nil {
console[mkResourceVirtualEnvironmentContainerConsoleMode] = *containerConfig.ConsoleMode
} else {
// Default value of "cmode" is "tty" according to the API documentation.
console[mkResourceVirtualEnvironmentContainerConsoleMode] = "tty"
}
if containerConfig.TTY != nil {
console[mkResourceVirtualEnvironmentContainerConsoleTTYCount] = *containerConfig.TTY
} else {
// Default value of "tty" is "2" according to the API documentation.
console[mkResourceVirtualEnvironmentContainerConsoleTTYCount] = 2
}
currentConsole := d.Get(mkResourceVirtualEnvironmentContainerConsole).([]interface{})
if len(clone) > 0 {
if len(currentConsole) > 0 {
err := d.Set(mkResourceVirtualEnvironmentContainerConsole, []interface{}{console})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentConsole) > 0 ||
//nolint:lll
console[mkResourceVirtualEnvironmentContainerConsoleEnabled] != types.CustomBool(dvResourceVirtualEnvironmentContainerConsoleEnabled) ||
console[mkResourceVirtualEnvironmentContainerConsoleMode] != dvResourceVirtualEnvironmentContainerConsoleMode ||
console[mkResourceVirtualEnvironmentContainerConsoleTTYCount] != dvResourceVirtualEnvironmentContainerConsoleTTYCount {
err := d.Set(mkResourceVirtualEnvironmentContainerConsole, []interface{}{console})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the CPU configuration to the one stored in the state.
cpu := map[string]interface{}{}
if containerConfig.CPUArchitecture != nil {
cpu[mkResourceVirtualEnvironmentContainerCPUArchitecture] = *containerConfig.CPUArchitecture
} else {
// Default value of "arch" is "amd64" according to the API documentation.
cpu[mkResourceVirtualEnvironmentContainerCPUArchitecture] = "amd64"
}
if containerConfig.CPUCores != nil {
cpu[mkResourceVirtualEnvironmentContainerCPUCores] = *containerConfig.CPUCores
} else {
// Default value of "cores" is "1" according to the API documentation.
cpu[mkResourceVirtualEnvironmentContainerCPUCores] = 1
}
if containerConfig.CPUUnits != nil {
cpu[mkResourceVirtualEnvironmentContainerCPUUnits] = *containerConfig.CPUUnits
} else {
// Default value of "cpuunits" is "1024" according to the API documentation.
cpu[mkResourceVirtualEnvironmentContainerCPUUnits] = 1024
}
currentCPU := d.Get(mkResourceVirtualEnvironmentContainerCPU).([]interface{})
if len(clone) > 0 {
if len(currentCPU) > 0 {
err := d.Set(mkResourceVirtualEnvironmentContainerCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentCPU) > 0 ||
cpu[mkResourceVirtualEnvironmentContainerCPUArchitecture] != dvResourceVirtualEnvironmentContainerCPUArchitecture ||
cpu[mkResourceVirtualEnvironmentContainerCPUCores] != dvResourceVirtualEnvironmentContainerCPUCores ||
cpu[mkResourceVirtualEnvironmentContainerCPUUnits] != dvResourceVirtualEnvironmentContainerCPUUnits {
err := d.Set(mkResourceVirtualEnvironmentContainerCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the disk configuration to the one stored in the state.
disk := map[string]interface{}{}
if containerConfig.RootFS != nil {
volumeParts := strings.Split(containerConfig.RootFS.Volume, ":")
disk[mkResourceVirtualEnvironmentContainerDiskDatastoreID] = volumeParts[0]
disk[mkResourceVirtualEnvironmentContainerDiskSize] = containerConfig.RootFS.Size.InGigabytes()
} else {
// Default value of "storage" is "local" according to the API documentation.
disk[mkResourceVirtualEnvironmentContainerDiskDatastoreID] = "local"
disk[mkResourceVirtualEnvironmentContainerDiskSize] = dvResourceVirtualEnvironmentContainerDiskSize
}
currentDisk := d.Get(mkResourceVirtualEnvironmentContainerDisk).([]interface{})
if len(clone) > 0 {
if len(currentDisk) > 0 {
// do not override the rootfs size if it was not changed during the clone operation
//nolint:lll
if currentDisk[0].(map[string]interface{})[mkResourceVirtualEnvironmentContainerDiskSize] == dvResourceVirtualEnvironmentContainerDiskSize {
disk[mkResourceVirtualEnvironmentContainerDiskSize] = dvResourceVirtualEnvironmentContainerDiskSize
}
err := d.Set(mkResourceVirtualEnvironmentContainerDisk, []interface{}{disk})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentDisk) > 0 ||
disk[mkResourceVirtualEnvironmentContainerDiskDatastoreID] != dvResourceVirtualEnvironmentContainerDiskDatastoreID ||
disk[mkResourceVirtualEnvironmentContainerDiskSize] != dvResourceVirtualEnvironmentContainerDiskSize {
err := d.Set(mkResourceVirtualEnvironmentContainerDisk, []interface{}{disk})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the memory configuration to the one stored in the state.
memory := map[string]interface{}{}
if containerConfig.DedicatedMemory != nil {
memory[mkResourceVirtualEnvironmentContainerMemoryDedicated] = *containerConfig.DedicatedMemory
} else {
memory[mkResourceVirtualEnvironmentContainerMemoryDedicated] = 0
}
if containerConfig.Swap != nil {
memory[mkResourceVirtualEnvironmentContainerMemorySwap] = *containerConfig.Swap
} else {
memory[mkResourceVirtualEnvironmentContainerMemorySwap] = 0
}
currentMemory := d.Get(mkResourceVirtualEnvironmentContainerMemory).([]interface{})
if len(clone) > 0 {
if len(currentMemory) > 0 {
err := d.Set(mkResourceVirtualEnvironmentContainerMemory, []interface{}{memory})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentMemory) > 0 ||
memory[mkResourceVirtualEnvironmentContainerMemoryDedicated] != dvResourceVirtualEnvironmentContainerMemoryDedicated ||
memory[mkResourceVirtualEnvironmentContainerMemorySwap] != dvResourceVirtualEnvironmentContainerMemorySwap {
err := d.Set(mkResourceVirtualEnvironmentContainerMemory, []interface{}{memory})
diags = append(diags, diag.FromErr(err)...)
}
// Compare the initialization and network interface configuration to the one stored in the state.
initialization := map[string]interface{}{}
if containerConfig.DNSDomain != nil || containerConfig.DNSServer != nil {
initializationDNS := map[string]interface{}{}
if containerConfig.DNSDomain != nil {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSDomain] = *containerConfig.DNSDomain
} else {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSDomain] = ""
}
if containerConfig.DNSServer != nil {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSServer] = *containerConfig.DNSServer
} else {
initializationDNS[mkResourceVirtualEnvironmentContainerInitializationDNSServer] = ""
}
initialization[mkResourceVirtualEnvironmentContainerInitializationDNS] = []interface{}{
initializationDNS,
}
}
if containerConfig.Hostname != nil {
initialization[mkResourceVirtualEnvironmentContainerInitializationHostname] = *containerConfig.Hostname
} else {
initialization[mkResourceVirtualEnvironmentContainerInitializationHostname] = ""
}
var ipConfigList []interface{}
networkInterfaceArray := []*containers.CustomNetworkInterface{
containerConfig.NetworkInterface0,
containerConfig.NetworkInterface1,
containerConfig.NetworkInterface2,
containerConfig.NetworkInterface3,
containerConfig.NetworkInterface4,
containerConfig.NetworkInterface5,
containerConfig.NetworkInterface6,
containerConfig.NetworkInterface7,
}
var networkInterfaceList []interface{}
for _, nv := range networkInterfaceArray {
if nv == nil {
continue
}
//nolint:nestif
if nv.IPv4Address != nil || nv.IPv4Gateway != nil || nv.IPv6Address != nil ||
nv.IPv6Gateway != nil {
ipConfig := map[string]interface{}{}
if nv.IPv4Address != nil || nv.IPv4Gateway != nil {
ip := map[string]interface{}{}
if nv.IPv4Address != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address] = *nv.IPv4Address
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Address] = ""
}
if nv.IPv4Gateway != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway] = *nv.IPv4Gateway
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4Gateway] = ""
}
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4] = []interface{}{
ip,
}
} else {
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv4] = []interface{}{}
}
if nv.IPv6Address != nil || nv.IPv6Gateway != nil {
ip := map[string]interface{}{}
if nv.IPv6Address != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address] = *nv.IPv6Address
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Address] = ""
}
if nv.IPv6Gateway != nil {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway] = *nv.IPv6Gateway
} else {
ip[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6Gateway] = ""
}
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6] = []interface{}{
ip,
}
} else {
ipConfig[mkResourceVirtualEnvironmentContainerInitializationIPConfigIPv6] = []interface{}{}
}
ipConfigList = append(ipConfigList, ipConfig)
}
networkInterface := map[string]interface{}{}
if nv.Bridge != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge] = *nv.Bridge
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge] = ""
}
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled] = true
if nv.Firewall != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall] = *nv.Firewall
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall] = false
}
if nv.MACAddress != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress] = *nv.MACAddress
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress] = ""
}
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceName] = nv.Name
if nv.RateLimit != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit] = *nv.RateLimit
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit] = 0
}
if nv.Tag != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID] = *nv.Tag
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID] = 0
}
if nv.MTU != nil {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU] = *nv.MTU
} else {
networkInterface[mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU] = 0
}
networkInterfaceList = append(networkInterfaceList, networkInterface)
}
initialization[mkResourceVirtualEnvironmentContainerInitializationIPConfig] = ipConfigList
currentInitialization := d.Get(mkResourceVirtualEnvironmentContainerInitialization).([]interface{})
if len(currentInitialization) > 0 {
currentInitializationMap := currentInitialization[0].(map[string]interface{})
initialization[mkResourceVirtualEnvironmentContainerInitializationUserAccount] = currentInitializationMap[mkResourceVirtualEnvironmentContainerInitializationUserAccount].([]interface{})
}
if len(clone) > 0 {
if len(currentInitialization) > 0 {
currentInitializationBlock := currentInitialization[0].(map[string]interface{})
currentInitializationDNS := currentInitializationBlock[mkResourceVirtualEnvironmentContainerInitializationDNS].([]interface{})
if len(currentInitializationDNS) == 0 {
initialization[mkResourceVirtualEnvironmentContainerInitializationDNS] = []interface{}{}
}
currentInitializationIPConfig := currentInitializationBlock[mkResourceVirtualEnvironmentContainerInitializationIPConfig].([]interface{})
if len(currentInitializationIPConfig) == 0 {
initialization[mkResourceVirtualEnvironmentContainerInitializationIPConfig] = []interface{}{}
}
currentInitializationUserAccount := currentInitializationBlock[mkResourceVirtualEnvironmentContainerInitializationUserAccount].([]interface{})
if len(currentInitializationUserAccount) == 0 {
initialization[mkResourceVirtualEnvironmentContainerInitializationUserAccount] = []interface{}{}
}
if len(initialization) > 0 {
e = d.Set(
mkResourceVirtualEnvironmentContainerInitialization,
[]interface{}{initialization},
)
} else {
e = d.Set(mkResourceVirtualEnvironmentContainerInitialization, []interface{}{})
}
diags = append(diags, diag.FromErr(e)...)
}
currentNetworkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{})
if len(currentNetworkInterface) > 0 {
err := d.Set(
mkResourceVirtualEnvironmentContainerNetworkInterface,
networkInterfaceList,
)
diags = append(diags, diag.FromErr(err)...)
}
} else {
if len(initialization) > 0 {
e = d.Set(mkResourceVirtualEnvironmentContainerInitialization, []interface{}{initialization})
} else {
e = d.Set(mkResourceVirtualEnvironmentContainerInitialization, []interface{}{})
}
diags = append(diags, diag.FromErr(e)...)
err := d.Set(mkResourceVirtualEnvironmentContainerNetworkInterface, networkInterfaceList)
diags = append(diags, diag.FromErr(err)...)
}
// Compare the operating system configuration to the one stored in the state.
operatingSystem := map[string]interface{}{}
if containerConfig.OSType != nil {
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType] = *containerConfig.OSType
} else {
// Default value of "ostype" is "" according to the API documentation.
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType] = ""
}
currentOperatingSystem := d.Get(mkResourceVirtualEnvironmentContainerOperatingSystem).([]interface{})
if len(currentOperatingSystem) > 0 {
currentOperatingSystemMap := currentOperatingSystem[0].(map[string]interface{})
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID] = currentOperatingSystemMap[mkResourceVirtualEnvironmentContainerOperatingSystemTemplateFileID]
}
if len(clone) > 0 {
if len(currentMemory) > 0 {
err := d.Set(
mkResourceVirtualEnvironmentContainerOperatingSystem,
[]interface{}{operatingSystem},
)
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentOperatingSystem) > 0 ||
operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType] != dvResourceVirtualEnvironmentContainerOperatingSystemType {
err := d.Set(mkResourceVirtualEnvironmentContainerOperatingSystem, []interface{}{operatingSystem})
diags = append(diags, diag.FromErr(err)...)
}
currentTags := d.Get(mkResourceVirtualEnvironmentContainerTags).([]interface{})
if len(clone) == 0 || len(currentTags) > 0 {
var tags []string
if containerConfig.Tags != nil {
for _, tag := range strings.Split(*containerConfig.Tags, ";") {
t := strings.TrimSpace(tag)
if len(t) > 0 {
tags = append(tags, t)
}
}
sort.Strings(tags)
}
e = d.Set(mkResourceVirtualEnvironmentContainerTags, tags)
diags = append(diags, diag.FromErr(e)...)
}
currentTemplate := d.Get(mkResourceVirtualEnvironmentContainerTemplate).(bool)
//nolint:gosimple
if len(clone) == 0 || currentTemplate != dvResourceVirtualEnvironmentContainerTemplate {
if containerConfig.Template != nil {
e = d.Set(
mkResourceVirtualEnvironmentContainerTemplate,
bool(*containerConfig.Template),
)
} else {
e = d.Set(mkResourceVirtualEnvironmentContainerTemplate, false)
}
diags = append(diags, diag.FromErr(e)...)
}
// Determine the state of the container in order to update the "started" argument.
status, e := containerAPI.GetContainerStatus(ctx)
if e != nil {
return diag.FromErr(e)
}
e = d.Set(mkResourceVirtualEnvironmentContainerStarted, status.Status == "running")
diags = append(diags, diag.FromErr(e)...)
return diags
}
func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, e := config.GetClient()
if e != nil {
return diag.FromErr(e)
}
nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string)
vmID, e := strconv.Atoi(d.Id())
if e != nil {
return diag.FromErr(e)
}
containerAPI := api.Node(nodeName).Container(vmID)
// Prepare the new request object.
updateBody := containers.UpdateRequestBody{
Delete: []string{},
}
rebootRequired := false
resource := Container()
// Retrieve the clone argument as the update logic varies for clones.
clone := d.Get(mkResourceVirtualEnvironmentVMClone).([]interface{})
// Prepare the new primitive values.
description := d.Get(mkResourceVirtualEnvironmentContainerDescription).(string)
updateBody.Description = &description
template := types.CustomBool(d.Get(mkResourceVirtualEnvironmentContainerTemplate).(bool))
if d.HasChange(mkResourceVirtualEnvironmentContainerTemplate) {
updateBody.Template = &template
}
// Prepare the new console configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerConsole) {
consoleBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerConsole},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
consoleEnabled := types.CustomBool(
consoleBlock[mkResourceVirtualEnvironmentContainerConsoleEnabled].(bool),
)
consoleMode := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleMode].(string)
consoleTTYCount := consoleBlock[mkResourceVirtualEnvironmentContainerConsoleTTYCount].(int)
updateBody.ConsoleEnabled = &consoleEnabled
updateBody.ConsoleMode = &consoleMode
updateBody.TTY = &consoleTTYCount
rebootRequired = true
}
// Prepare the new CPU configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerCPU) {
cpuBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerCPU},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cpuArchitecture := cpuBlock[mkResourceVirtualEnvironmentContainerCPUArchitecture].(string)
cpuCores := cpuBlock[mkResourceVirtualEnvironmentContainerCPUCores].(int)
cpuUnits := cpuBlock[mkResourceVirtualEnvironmentContainerCPUUnits].(int)
updateBody.CPUArchitecture = &cpuArchitecture
updateBody.CPUCores = &cpuCores
updateBody.CPUUnits = &cpuUnits
rebootRequired = true
}
// Prepare the new initialization configuration.
initialization := d.Get(mkResourceVirtualEnvironmentContainerInitialization).([]interface{})
initializationDNSDomain := dvResourceVirtualEnvironmentContainerInitializationDNSDomain
initializationDNSServer := dvResourceVirtualEnvironmentContainerInitializationDNSServer
initializationHostname := dvResourceVirtualEnvironmentContainerInitializationHostname
var initializationIPConfigIPv4Address []string
var initializationIPConfigIPv4Gateway []string
var initializationIPConfigIPv6Address []string
var initializationIPConfigIPv6Gateway []string
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, "")
}
}
}
if d.HasChange(mkResourceVirtualEnvironmentContainerInitialization) {
updateBody.DNSDomain = &initializationDNSDomain
updateBody.DNSServer = &initializationDNSServer
updateBody.Hostname = &initializationHostname
rebootRequired = true
}
// Prepare the new memory configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerMemory) {
memoryBlock, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerMemory},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
memoryDedicated := memoryBlock[mkResourceVirtualEnvironmentContainerMemoryDedicated].(int)
memorySwap := memoryBlock[mkResourceVirtualEnvironmentContainerMemorySwap].(int)
updateBody.DedicatedMemory = &memoryDedicated
updateBody.Swap = &memorySwap
rebootRequired = true
}
// Prepare the new network interface configuration.
networkInterface := d.Get(mkResourceVirtualEnvironmentContainerNetworkInterface).([]interface{})
if len(networkInterface) == 0 && len(clone) > 0 {
networkInterface, e = containerGetExistingNetworkInterface(ctx, containerAPI)
if e != nil {
return diag.FromErr(e)
}
}
//nolint:nestif
if d.HasChange(mkResourceVirtualEnvironmentContainerInitialization) ||
d.HasChange(mkResourceVirtualEnvironmentContainerNetworkInterface) {
networkInterfaceArray := make(
containers.CustomNetworkInterfaceArray,
len(networkInterface),
)
for ni, nv := range networkInterface {
networkInterfaceMap := nv.(map[string]interface{})
networkInterfaceObject := containers.CustomNetworkInterface{}
bridge := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceBridge].(string)
enabled := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceEnabled].(bool)
firewall := types.CustomBool(
networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceFirewall].(bool),
)
macAddress := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMACAddress].(string)
name := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceName].(string)
rateLimit := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceRateLimit].(float64)
vlanID := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceVLANID].(int)
mtu := networkInterfaceMap[mkResourceVirtualEnvironmentContainerNetworkInterfaceMTU].(int)
if bridge != "" {
networkInterfaceObject.Bridge = &bridge
}
networkInterfaceObject.Enabled = enabled
networkInterfaceObject.Firewall = &firewall
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
}
networkInterfaceObject.Name = name
if rateLimit != 0 {
networkInterfaceObject.RateLimit = &rateLimit
}
if vlanID != 0 {
networkInterfaceObject.Tag = &vlanID
}
if mtu != 0 {
networkInterfaceObject.MTU = &mtu
}
networkInterfaceArray[ni] = networkInterfaceObject
}
updateBody.NetworkInterfaces = networkInterfaceArray
for i := 0; i < len(updateBody.NetworkInterfaces); i++ {
if !updateBody.NetworkInterfaces[i].Enabled {
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
}
}
for i := len(updateBody.NetworkInterfaces); i < maxResourceVirtualEnvironmentContainerNetworkInterfaces; i++ {
updateBody.Delete = append(updateBody.Delete, fmt.Sprintf("net%d", i))
}
rebootRequired = true
}
// Prepare the new operating system configuration.
if d.HasChange(mkResourceVirtualEnvironmentContainerOperatingSystem) {
operatingSystem, err := getSchemaBlock(
resource,
d,
[]string{mkResourceVirtualEnvironmentContainerOperatingSystem},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
operatingSystemType := operatingSystem[mkResourceVirtualEnvironmentContainerOperatingSystemType].(string)
updateBody.OSType = &operatingSystemType
rebootRequired = true
}
if d.HasChange(mkResourceVirtualEnvironmentContainerTags) {
tagString := containerGetTagsString(d)
updateBody.Tags = &tagString
}
// Update the configuration now that everything has been prepared.
e = containerAPI.UpdateContainer(ctx, &updateBody)
if e != nil {
return diag.FromErr(e)
}
// Determine if the state of the container needs to be changed.
started := d.Get(mkResourceVirtualEnvironmentContainerStarted).(bool)
if d.HasChange(mkResourceVirtualEnvironmentContainerStarted) && !bool(template) {
if started {
e = containerAPI.StartContainer(ctx)
if e != nil {
return diag.FromErr(e)
}
e = containerAPI.WaitForContainerState(ctx, "running", 300, 5)
if e != nil {
return diag.FromErr(e)
}
} else {
forceStop := types.CustomBool(true)
shutdownTimeout := 300
e = containerAPI.ShutdownContainer(ctx, &containers.ShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
})
if e != nil {
return diag.FromErr(e)
}
e = containerAPI.WaitForContainerState(ctx, "stopped", 300, 5)
if e != nil {
return diag.FromErr(e)
}
rebootRequired = false
}
}
// As a final step in the update procedure, we might need to reboot the container.
if !bool(template) && rebootRequired {
rebootTimeout := 300
e = containerAPI.RebootContainer(
ctx,
&containers.RebootRequestBody{
Timeout: &rebootTimeout,
},
)
if e != nil {
return diag.FromErr(e)
}
}
return containerRead(ctx, d, m)
}
func containerDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
config := m.(proxmoxtf.ProviderConfiguration)
api, err := config.GetClient()
if err != nil {
return diag.FromErr(err)
}
nodeName := d.Get(mkResourceVirtualEnvironmentContainerNodeName).(string)
vmID, err := strconv.Atoi(d.Id())
if err != nil {
return diag.FromErr(err)
}
containerAPI := api.Node(nodeName).Container(vmID)
// Shut down the container before deleting it.
status, err := containerAPI.GetContainerStatus(ctx)
if err != nil {
return diag.FromErr(err)
}
if status.Status != "stopped" {
forceStop := types.CustomBool(true)
shutdownTimeout := 300
err = containerAPI.ShutdownContainer(
ctx,
&containers.ShutdownRequestBody{
ForceStop: &forceStop,
Timeout: &shutdownTimeout,
},
)
if err != nil {
return diag.FromErr(err)
}
err = containerAPI.WaitForContainerState(ctx, "stopped", 30, 5)
if err != nil {
return diag.FromErr(err)
}
}
err = containerAPI.DeleteContainer(ctx)
if err != nil {
if strings.Contains(err.Error(), "HTTP 404") {
d.SetId("")
return nil
}
return diag.FromErr(err)
}
// Wait for the state to become unavailable as that clearly indicates the destruction of the container.
err = containerAPI.WaitForContainerState(ctx, "", 60, 2)
if err == nil {
return diag.Errorf("failed to delete container \"%d\"", vmID)
}
d.SetId("")
return nil
}