mirror of
https://github.com/letic/terraform-provider-proxmox.git
synced 2024-10-05 02:01:04 +00:00
Merge pull request #13 from AAbouZaid/multi_device
[WIP] Add support for multi device.
This commit is contained in:
commit
95d52ed4d6
26
README.md
26
README.md
@ -1,4 +1,4 @@
|
|||||||
# Proxmox 4 Terraform
|
# Proxmox 4 Terraform
|
||||||
|
|
||||||
Terraform provider plugin for proxmox
|
Terraform provider plugin for proxmox
|
||||||
|
|
||||||
@ -78,13 +78,27 @@ resource "proxmox_vm_qemu" "prepprovision-test" {
|
|||||||
target_node = "proxmox1-xx"
|
target_node = "proxmox1-xx"
|
||||||
|
|
||||||
clone = "terraform-ubuntu1404-template"
|
clone = "terraform-ubuntu1404-template"
|
||||||
storage = "local"
|
|
||||||
cores = 3
|
cores = 3
|
||||||
sockets = 1
|
sockets = 1
|
||||||
memory = 2560
|
memory = 2560
|
||||||
disk_gb = 4
|
network {
|
||||||
nic = "virtio"
|
id = 0
|
||||||
bridge = "vmbr1"
|
model = "virtio"
|
||||||
|
}
|
||||||
|
network {
|
||||||
|
id = 1
|
||||||
|
model = "virtio"
|
||||||
|
bridge = "vmbr1"
|
||||||
|
}
|
||||||
|
disk {
|
||||||
|
id = 0
|
||||||
|
type = virtio
|
||||||
|
storage = local-lvm
|
||||||
|
storage_type = lvm
|
||||||
|
size = 4G
|
||||||
|
backup = true
|
||||||
|
}
|
||||||
|
preprovision = true
|
||||||
ssh_forward_ip = "10.0.0.1"
|
ssh_forward_ip = "10.0.0.1"
|
||||||
ssh_user = "terraform"
|
ssh_user = "terraform"
|
||||||
ssh_private_key = <<EOF
|
ssh_private_key = <<EOF
|
||||||
@ -117,7 +131,7 @@ You can start from either an ISO or clone an existing VM.
|
|||||||
Optimally, you could create a VM resource you will use a clone base with an ISO, and make the rest of the VM resources depend on that base "template" and clone it.
|
Optimally, you could create a VM resource you will use a clone base with an ISO, and make the rest of the VM resources depend on that base "template" and clone it.
|
||||||
|
|
||||||
Interesting parameters:
|
Interesting parameters:
|
||||||
|
**preprovision** - to enable or disable internal pre-provisioning (e.g. if you already have another way to provision VMs). Conflicts with: `ssh_forward_ip`, `ssh_user`, `ssh_private_key`, `os_type`, `os_network_config`.
|
||||||
**os_type** -
|
**os_type** -
|
||||||
* cloud-init - from Proxmox 5.2
|
* cloud-init - from Proxmox 5.2
|
||||||
* ubuntu -(https://github.com/Telmate/terraform-ubuntu-proxmox-iso)
|
* ubuntu -(https://github.com/Telmate/terraform-ubuntu-proxmox-iso)
|
||||||
|
@ -61,8 +61,9 @@ func applyFn(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
var vmParams map[string]interface{}
|
vmParams := map[string]interface{}{
|
||||||
vmParams["net1"] = data.Get("net1").(string)
|
"net1": data.Get("net1").(string),
|
||||||
|
}
|
||||||
_, err = client.SetVmConfig(vmr, vmParams)
|
_, err = client.SetVmConfig(vmr, vmParams)
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
return err
|
return err
|
||||||
|
@ -42,9 +42,10 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
"ssh_forward_ip": {
|
"onboot": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
},
|
},
|
||||||
"iso": {
|
"iso": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
@ -56,31 +57,10 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
"storage": {
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
"storage_type": {
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
ForceNew: false,
|
|
||||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
|
||||||
if new == "" {
|
|
||||||
return true // empty template ok
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"qemu_os": {
|
"qemu_os": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: "l26",
|
Default: "l26",
|
||||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
|
||||||
if old == "" {
|
|
||||||
return true // reading empty ok
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
@ -94,9 +74,120 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
"network": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
ConflictsWith: []string{"nic", "bridge", "vlan", "mac"},
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"model": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"macaddr": &schema.Schema{
|
||||||
|
// TODO: Find a way to set MAC address in .tf config.
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"bridge": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "nat",
|
||||||
|
},
|
||||||
|
"tag": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Description: "VLAN tag.",
|
||||||
|
Default: -1,
|
||||||
|
},
|
||||||
|
"firewall": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"rate": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: -1,
|
||||||
|
},
|
||||||
|
"queues": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Default: -1,
|
||||||
|
},
|
||||||
|
"link_down": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"disk": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
ConflictsWith: []string{"disk_gb", "storage", "storage_type"},
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"storage": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"storage_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "dir",
|
||||||
|
Description: "One of PVE types as described: https://pve.proxmox.com/wiki/Storage",
|
||||||
|
},
|
||||||
|
"size": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"format": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "raw",
|
||||||
|
},
|
||||||
|
"cache": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "none",
|
||||||
|
},
|
||||||
|
"backup": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"iothread": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"replicate": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Deprecated single disk config.
|
||||||
"disk_gb": {
|
"disk_gb": {
|
||||||
Type: schema.TypeFloat,
|
Type: schema.TypeFloat,
|
||||||
Required: true,
|
Deprecated: "Use `disk.size` instead",
|
||||||
|
Optional: true,
|
||||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||||
// bigger ok
|
// bigger ok
|
||||||
oldf, _ := strconv.ParseFloat(old, 64)
|
oldf, _ := strconv.ParseFloat(old, 64)
|
||||||
@ -104,18 +195,50 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
return oldf >= newf
|
return oldf >= newf
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"storage": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Deprecated: "Use `disk.storage` instead",
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"storage_type": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Deprecated: "Use `disk.type` instead",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: false,
|
||||||
|
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||||
|
if new == "" {
|
||||||
|
return true // empty template ok
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Deprecated single nic config.
|
||||||
"nic": {
|
"nic": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Deprecated: "Use `network` instead",
|
||||||
|
Optional: true,
|
||||||
},
|
},
|
||||||
"bridge": {
|
"bridge": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Deprecated: "Use `network.bridge` instead",
|
||||||
|
Optional: true,
|
||||||
},
|
},
|
||||||
"vlan": {
|
"vlan": {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Deprecated: "Use `network.tag` instead",
|
||||||
Default: -1,
|
Optional: true,
|
||||||
|
Default: -1,
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Deprecated: "Use `network.macaddr` to access the auto generated MAC address",
|
||||||
|
Optional: true,
|
||||||
|
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||||
|
if new == "" {
|
||||||
|
return true // macaddr auto-generates and its ok
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"os_type": {
|
"os_type": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
@ -129,6 +252,10 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"ssh_forward_ip": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"ssh_user": {
|
"ssh_user": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@ -147,16 +274,6 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Default: false,
|
Default: false,
|
||||||
},
|
},
|
||||||
"mac": {
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
|
||||||
if new == "" {
|
|
||||||
return true // macaddr auto-generates and its ok
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ci_wait": { // how long to wait before provision
|
"ci_wait": { // how long to wait before provision
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@ -199,6 +316,12 @@ func resourceVmQemu() *schema.Resource {
|
|||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"preprovision": {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
|
ConflictsWith: []string{"ssh_forward_ip", "ssh_user", "ssh_private_key", "os_type", "os_network_config"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,20 +333,22 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
pmParallelBegin(pconf)
|
pmParallelBegin(pconf)
|
||||||
client := pconf.Client
|
client := pconf.Client
|
||||||
vmName := d.Get("name").(string)
|
vmName := d.Get("name").(string)
|
||||||
diskGB := d.Get("disk_gb").(float64)
|
networks := d.Get("network").(*schema.Set)
|
||||||
|
qemuNetworks := devicesSetToMap(networks)
|
||||||
|
disks := d.Get("disk").(*schema.Set)
|
||||||
|
qemuDisks := devicesSetToMap(disks)
|
||||||
|
|
||||||
config := pxapi.ConfigQemu{
|
config := pxapi.ConfigQemu{
|
||||||
Name: vmName,
|
Name: vmName,
|
||||||
Description: d.Get("desc").(string),
|
Description: d.Get("desc").(string),
|
||||||
Storage: d.Get("storage").(string),
|
Onboot: d.Get("onboot").(bool),
|
||||||
Memory: d.Get("memory").(int),
|
Memory: d.Get("memory").(int),
|
||||||
QemuCores: d.Get("cores").(int),
|
QemuCores: d.Get("cores").(int),
|
||||||
QemuSockets: d.Get("sockets").(int),
|
QemuSockets: d.Get("sockets").(int),
|
||||||
DiskSize: diskGB,
|
|
||||||
QemuOs: d.Get("qemu_os").(string),
|
QemuOs: d.Get("qemu_os").(string),
|
||||||
QemuNicModel: d.Get("nic").(string),
|
QemuNetworks: qemuNetworks,
|
||||||
QemuBrige: d.Get("bridge").(string),
|
QemuDisks: qemuDisks,
|
||||||
QemuVlanTag: d.Get("vlan").(int),
|
// Cloud-init.
|
||||||
QemuMacAddr: d.Get("mac").(string),
|
|
||||||
CIuser: d.Get("ciuser").(string),
|
CIuser: d.Get("ciuser").(string),
|
||||||
CIpassword: d.Get("cipassword").(string),
|
CIpassword: d.Get("cipassword").(string),
|
||||||
Searchdomain: d.Get("searchdomain").(string),
|
Searchdomain: d.Get("searchdomain").(string),
|
||||||
@ -231,6 +356,14 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
Sshkeys: d.Get("sshkeys").(string),
|
Sshkeys: d.Get("sshkeys").(string),
|
||||||
Ipconfig0: d.Get("ipconfig0").(string),
|
Ipconfig0: d.Get("ipconfig0").(string),
|
||||||
Ipconfig1: d.Get("ipconfig1").(string),
|
Ipconfig1: d.Get("ipconfig1").(string),
|
||||||
|
// Deprecated single disk config.
|
||||||
|
Storage: d.Get("storage").(string),
|
||||||
|
DiskSize: d.Get("disk_gb").(float64),
|
||||||
|
// Deprecated single nic config.
|
||||||
|
QemuNicModel: d.Get("nic").(string),
|
||||||
|
QemuBrige: d.Get("bridge").(string),
|
||||||
|
QemuVlanTag: d.Get("vlan").(int),
|
||||||
|
QemuMacAddr: d.Get("mac").(string),
|
||||||
}
|
}
|
||||||
log.Print("[DEBUG] checking for duplicate name")
|
log.Print("[DEBUG] checking for duplicate name")
|
||||||
dupVmr, _ := client.GetVmRefByName(vmName)
|
dupVmr, _ := client.GetVmRefByName(vmName)
|
||||||
@ -275,7 +408,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
// give sometime to proxmox to catchup
|
// give sometime to proxmox to catchup
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
err = prepareDiskSize(client, vmr, diskGB)
|
err = prepareDiskSize(client, vmr, qemuDisks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pmParallelEnd(pconf)
|
pmParallelEnd(pconf)
|
||||||
return err
|
return err
|
||||||
@ -303,7 +436,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
// give sometime to proxmox to catchup
|
// give sometime to proxmox to catchup
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
err = prepareDiskSize(client, vmr, diskGB)
|
err = prepareDiskSize(client, vmr, qemuDisks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pmParallelEnd(pconf)
|
pmParallelEnd(pconf)
|
||||||
return err
|
return err
|
||||||
@ -321,75 +454,8 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sshPort := "22"
|
// Apply pre-provision if enabled.
|
||||||
sshHost := ""
|
preprovision(d, pconf, client, vmr, true)
|
||||||
if config.HasCloudInit() {
|
|
||||||
if d.Get("ssh_forward_ip") != nil {
|
|
||||||
sshHost = d.Get("ssh_forward_ip").(string)
|
|
||||||
}
|
|
||||||
if sshHost == "" {
|
|
||||||
// parse IP address out of ipconfig0
|
|
||||||
ipMatch := rxIPconfig.FindStringSubmatch(d.Get("ipconfig0").(string))
|
|
||||||
sshHost = ipMatch[1]
|
|
||||||
|
|
||||||
}
|
|
||||||
// Check if we got a speficied port
|
|
||||||
if strings.Contains(sshHost, ":") {
|
|
||||||
sshParts := strings.Split(sshHost, ":")
|
|
||||||
sshHost = sshParts[0]
|
|
||||||
sshPort = sshParts[1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Print("[DEBUG] setting up SSH forward")
|
|
||||||
sshPort, err = pxapi.SshForwardUsernet(vmr, client)
|
|
||||||
if err != nil {
|
|
||||||
pmParallelEnd(pconf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sshHost = d.Get("ssh_forward_ip").(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done with proxmox API, end parallel and do the SSH things
|
|
||||||
pmParallelEnd(pconf)
|
|
||||||
|
|
||||||
d.SetConnInfo(map[string]string{
|
|
||||||
"type": "ssh",
|
|
||||||
"host": sshHost,
|
|
||||||
"port": sshPort,
|
|
||||||
"user": d.Get("ssh_user").(string),
|
|
||||||
"private_key": d.Get("ssh_private_key").(string),
|
|
||||||
"pm_api_url": client.ApiUrl,
|
|
||||||
"pm_user": client.Username,
|
|
||||||
"pm_password": client.Password,
|
|
||||||
"pm_tls_insecure": "true", // TODO - pass pm_tls_insecure state around, but if we made it this far, default insecure
|
|
||||||
})
|
|
||||||
|
|
||||||
switch d.Get("os_type").(string) {
|
|
||||||
|
|
||||||
case "ubuntu":
|
|
||||||
// give sometime to bootup
|
|
||||||
time.Sleep(9 * time.Second)
|
|
||||||
err = preProvisionUbuntu(d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case "centos":
|
|
||||||
// give sometime to bootup
|
|
||||||
time.Sleep(9 * time.Second)
|
|
||||||
err = preProvisionCentos(d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case "cloud-init":
|
|
||||||
// wait for OS too boot awhile...
|
|
||||||
log.Print("[DEBUG] sleeping for OS bootup...")
|
|
||||||
time.Sleep(time.Duration(d.Get("ci_wait").(int)) * time.Second)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unknown os_type: %s", d.Get("os_type").(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -404,20 +470,22 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
vmName := d.Get("name").(string)
|
vmName := d.Get("name").(string)
|
||||||
diskGB := d.Get("disk_gb").(float64)
|
configDisksSet := d.Get("disk").(*schema.Set)
|
||||||
|
qemuDisks := devicesSetToMap(configDisksSet)
|
||||||
|
configNetworksSet := d.Get("network").(*schema.Set)
|
||||||
|
qemuNetworks := devicesSetToMap(configNetworksSet)
|
||||||
|
|
||||||
config := pxapi.ConfigQemu{
|
config := pxapi.ConfigQemu{
|
||||||
Name: vmName,
|
Name: vmName,
|
||||||
Description: d.Get("desc").(string),
|
Description: d.Get("desc").(string),
|
||||||
Storage: d.Get("storage").(string),
|
Onboot: d.Get("onboot").(bool),
|
||||||
Memory: d.Get("memory").(int),
|
Memory: d.Get("memory").(int),
|
||||||
QemuCores: d.Get("cores").(int),
|
QemuCores: d.Get("cores").(int),
|
||||||
QemuSockets: d.Get("sockets").(int),
|
QemuSockets: d.Get("sockets").(int),
|
||||||
DiskSize: diskGB,
|
|
||||||
QemuOs: d.Get("qemu_os").(string),
|
QemuOs: d.Get("qemu_os").(string),
|
||||||
QemuNicModel: d.Get("nic").(string),
|
QemuNetworks: qemuNetworks,
|
||||||
QemuBrige: d.Get("bridge").(string),
|
QemuDisks: qemuDisks,
|
||||||
QemuVlanTag: d.Get("vlan").(int),
|
// Cloud-init.
|
||||||
QemuMacAddr: d.Get("mac").(string),
|
|
||||||
CIuser: d.Get("ciuser").(string),
|
CIuser: d.Get("ciuser").(string),
|
||||||
CIpassword: d.Get("cipassword").(string),
|
CIpassword: d.Get("cipassword").(string),
|
||||||
Searchdomain: d.Get("searchdomain").(string),
|
Searchdomain: d.Get("searchdomain").(string),
|
||||||
@ -425,6 +493,14 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
Sshkeys: d.Get("sshkeys").(string),
|
Sshkeys: d.Get("sshkeys").(string),
|
||||||
Ipconfig0: d.Get("ipconfig0").(string),
|
Ipconfig0: d.Get("ipconfig0").(string),
|
||||||
Ipconfig1: d.Get("ipconfig1").(string),
|
Ipconfig1: d.Get("ipconfig1").(string),
|
||||||
|
// Deprecated single disk config.
|
||||||
|
Storage: d.Get("storage").(string),
|
||||||
|
DiskSize: d.Get("disk_gb").(float64),
|
||||||
|
// Deprecated single nic config.
|
||||||
|
QemuNicModel: d.Get("nic").(string),
|
||||||
|
QemuBrige: d.Get("bridge").(string),
|
||||||
|
QemuVlanTag: d.Get("vlan").(int),
|
||||||
|
QemuMacAddr: d.Get("mac").(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = config.UpdateConfig(vmr, client)
|
err = config.UpdateConfig(vmr, client)
|
||||||
@ -436,31 +512,24 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error {
|
|||||||
// give sometime to proxmox to catchup
|
// give sometime to proxmox to catchup
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
prepareDiskSize(client, vmr, diskGB)
|
prepareDiskSize(client, vmr, qemuDisks)
|
||||||
|
|
||||||
// give sometime to proxmox to catchup
|
// give sometime to proxmox to catchup
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
log.Print("[DEBUG] starting VM")
|
// Start VM only if it wasn't running.
|
||||||
_, err = client.StartVm(vmr)
|
vmState, err := client.GetVmState(vmr)
|
||||||
|
if err == nil && vmState["status"] == "stopped" {
|
||||||
if err != nil {
|
log.Print("[DEBUG] starting VM")
|
||||||
|
_, err = client.StartVm(vmr)
|
||||||
|
} else if err != nil {
|
||||||
pmParallelEnd(pconf)
|
pmParallelEnd(pconf)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sshPort, err := pxapi.SshForwardUsernet(vmr, client)
|
// Apply pre-provision if enabled.
|
||||||
if err != nil {
|
preprovision(d, pconf, client, vmr, false)
|
||||||
pmParallelEnd(pconf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.SetConnInfo(map[string]string{
|
|
||||||
"type": "ssh",
|
|
||||||
"host": d.Get("ssh_forward_ip").(string),
|
|
||||||
"port": sshPort,
|
|
||||||
"user": d.Get("ssh_user").(string),
|
|
||||||
"private_key": d.Get("ssh_private_key").(string),
|
|
||||||
})
|
|
||||||
pmParallelEnd(pconf)
|
pmParallelEnd(pconf)
|
||||||
|
|
||||||
// give sometime to bootup
|
// give sometime to bootup
|
||||||
@ -485,18 +554,12 @@ func resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error {
|
|||||||
d.Set("target_node", vmr.Node())
|
d.Set("target_node", vmr.Node())
|
||||||
d.Set("name", config.Name)
|
d.Set("name", config.Name)
|
||||||
d.Set("desc", config.Description)
|
d.Set("desc", config.Description)
|
||||||
d.Set("storage", config.Storage)
|
d.Set("onboot", config.Onboot)
|
||||||
d.Set("storage_type", config.StorageType)
|
|
||||||
d.Set("memory", config.Memory)
|
d.Set("memory", config.Memory)
|
||||||
d.Set("cores", config.QemuCores)
|
d.Set("cores", config.QemuCores)
|
||||||
d.Set("sockets", config.QemuSockets)
|
d.Set("sockets", config.QemuSockets)
|
||||||
d.Set("disk_gb", config.DiskSize)
|
|
||||||
d.Set("qemu_os", config.QemuOs)
|
d.Set("qemu_os", config.QemuOs)
|
||||||
d.Set("nic", config.QemuNicModel)
|
// Cloud-init.
|
||||||
d.Set("bridge", config.QemuBrige)
|
|
||||||
d.Set("vlan", config.QemuVlanTag)
|
|
||||||
d.Set("mac", config.QemuMacAddr)
|
|
||||||
|
|
||||||
d.Set("ciuser", config.CIuser)
|
d.Set("ciuser", config.CIuser)
|
||||||
d.Set("cipassword", config.CIpassword)
|
d.Set("cipassword", config.CIpassword)
|
||||||
d.Set("searchdomain", config.Searchdomain)
|
d.Set("searchdomain", config.Searchdomain)
|
||||||
@ -504,6 +567,23 @@ func resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error {
|
|||||||
d.Set("sshkeys", config.Sshkeys)
|
d.Set("sshkeys", config.Sshkeys)
|
||||||
d.Set("ipconfig0", config.Ipconfig0)
|
d.Set("ipconfig0", config.Ipconfig0)
|
||||||
d.Set("ipconfig1", config.Ipconfig1)
|
d.Set("ipconfig1", config.Ipconfig1)
|
||||||
|
// Disks.
|
||||||
|
configDisksSet := d.Get("disk").(*schema.Set)
|
||||||
|
activeDisksSet := updateDevicesSet(configDisksSet, config.QemuDisks)
|
||||||
|
d.Set("disk", activeDisksSet)
|
||||||
|
// Networks.
|
||||||
|
configNetworksSet := d.Get("network").(*schema.Set)
|
||||||
|
activeNetworksSet := updateDevicesSet(configNetworksSet, config.QemuNetworks)
|
||||||
|
d.Set("network", activeNetworksSet)
|
||||||
|
// Deprecated single disk config.
|
||||||
|
d.Set("storage", config.Storage)
|
||||||
|
d.Set("disk_gb", config.DiskSize)
|
||||||
|
d.Set("storage_type", config.StorageType)
|
||||||
|
// Deprecated single nic config.
|
||||||
|
d.Set("nic", config.QemuNicModel)
|
||||||
|
d.Set("bridge", config.QemuBrige)
|
||||||
|
d.Set("vlan", config.QemuVlanTag)
|
||||||
|
d.Set("mac", config.QemuMacAddr)
|
||||||
|
|
||||||
pmParallelEnd(pconf)
|
pmParallelEnd(pconf)
|
||||||
return nil
|
return nil
|
||||||
@ -533,18 +613,181 @@ func resourceVmQemuDelete(d *schema.ResourceData, meta interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareDiskSize(client *pxapi.Client, vmr *pxapi.VmRef, diskGB float64) error {
|
// Increase disk size if original disk was smaller than new disk.
|
||||||
|
func prepareDiskSize(
|
||||||
|
client *pxapi.Client,
|
||||||
|
vmr *pxapi.VmRef,
|
||||||
|
diskConfMap pxapi.QemuDevices,
|
||||||
|
) error {
|
||||||
clonedConfig, err := pxapi.NewConfigQemuFromApi(vmr, client)
|
clonedConfig, err := pxapi.NewConfigQemuFromApi(vmr, client)
|
||||||
if err != nil {
|
for _, diskConf := range diskConfMap {
|
||||||
return err
|
diskID := diskConf["id"].(int)
|
||||||
}
|
diskName := fmt.Sprintf("%v%v", diskConf["type"], diskID)
|
||||||
if diskGB > clonedConfig.DiskSize {
|
|
||||||
log.Print("[DEBUG] resizing disk " + clonedConfig.StorageType)
|
diskSizeGB := diskConf["size"].(string)
|
||||||
diffSize := int(math.Ceil(diskGB - clonedConfig.DiskSize))
|
diskSize, _ := strconv.ParseFloat(strings.Trim(diskSizeGB, "G"), 64)
|
||||||
_, err = client.ResizeQemuDisk(vmr, clonedConfig.StorageType+"0", diffSize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, diskExists := clonedConfig.QemuDisks[diskID]; !diskExists {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clonedDiskSizeGB := clonedConfig.QemuDisks[diskID]["size"].(string)
|
||||||
|
clonedDiskSize, _ := strconv.ParseFloat(strings.Trim(clonedDiskSizeGB, "G"), 64)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diffSize := int(math.Ceil(diskSize - clonedDiskSize))
|
||||||
|
if diskSize > clonedDiskSize {
|
||||||
|
log.Print("[DEBUG] resizing disk " + diskName)
|
||||||
|
_, err = client.ResizeQemuDisk(vmr, diskName, diffSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converting from schema.TypeSet to map of id and conf for each device,
|
||||||
|
// which will be sent to Proxmox API.
|
||||||
|
func devicesSetToMap(devicesSet *schema.Set) pxapi.QemuDevices {
|
||||||
|
|
||||||
|
devicesMap := pxapi.QemuDevices{}
|
||||||
|
|
||||||
|
for _, set := range devicesSet.List() {
|
||||||
|
setMap, isMap := set.(map[string]interface{})
|
||||||
|
if isMap {
|
||||||
|
setID := setMap["id"].(int)
|
||||||
|
devicesMap[setID] = setMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devicesMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update schema.TypeSet with new values comes from Proxmox API.
|
||||||
|
// TODO: Maybe it's better to create a new Set instead add to current one.
|
||||||
|
func updateDevicesSet(
|
||||||
|
devicesSet *schema.Set,
|
||||||
|
devicesMap pxapi.QemuDevices,
|
||||||
|
) *schema.Set {
|
||||||
|
|
||||||
|
configDevicesMap := devicesSetToMap(devicesSet)
|
||||||
|
activeDevicesMap := updateDevicesDefaults(devicesMap, configDevicesMap)
|
||||||
|
|
||||||
|
for _, setConf := range devicesSet.List() {
|
||||||
|
devicesSet.Remove(setConf)
|
||||||
|
setConfMap := setConf.(map[string]interface{})
|
||||||
|
deviceID := setConfMap["id"].(int)
|
||||||
|
// Value type should be one of types allowed by Terraform schema types.
|
||||||
|
for key, value := range activeDevicesMap[deviceID] {
|
||||||
|
// This nested switch is used for nested config like in `net[n]`,
|
||||||
|
// where Proxmox uses `key=<0|1>` in string" at the same time
|
||||||
|
// a boolean could be used in ".tf" files.
|
||||||
|
switch setConfMap[key].(type) {
|
||||||
|
case bool:
|
||||||
|
switch value.(type) {
|
||||||
|
// If the key is bool and value is int (which comes from Proxmox API),
|
||||||
|
// should be converted to bool (as in ".tf" conf).
|
||||||
|
case int:
|
||||||
|
sValue := strconv.Itoa(value.(int))
|
||||||
|
bValue, err := strconv.ParseBool(sValue)
|
||||||
|
if err == nil {
|
||||||
|
setConfMap[key] = bValue
|
||||||
|
}
|
||||||
|
// If value is bool, which comes from Terraform conf, add it directly.
|
||||||
|
case bool:
|
||||||
|
setConfMap[key] = value
|
||||||
|
}
|
||||||
|
// Anything else will be added as it is.
|
||||||
|
default:
|
||||||
|
setConfMap[key] = value
|
||||||
|
}
|
||||||
|
devicesSet.Add(setConfMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return devicesSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because default values are not stored in Proxmox, so the API returns only active values.
|
||||||
|
// So to prevent Terraform doing unnecessary diffs, this function reads default values
|
||||||
|
// from Terraform itself, and fill empty fields.
|
||||||
|
func updateDevicesDefaults(
|
||||||
|
activeDevicesMap pxapi.QemuDevices,
|
||||||
|
configDevicesMap pxapi.QemuDevices,
|
||||||
|
) pxapi.QemuDevices {
|
||||||
|
|
||||||
|
for deviceID, deviceConf := range configDevicesMap {
|
||||||
|
if _, ok := activeDevicesMap[deviceID]; !ok {
|
||||||
|
activeDevicesMap[deviceID] = configDevicesMap[deviceID]
|
||||||
|
}
|
||||||
|
for key, value := range deviceConf {
|
||||||
|
if _, ok := activeDevicesMap[deviceID][key]; !ok {
|
||||||
|
activeDevicesMap[deviceID][key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return activeDevicesMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal pre-provision.
|
||||||
|
func preprovision(
|
||||||
|
d *schema.ResourceData,
|
||||||
|
pconf *providerConfiguration,
|
||||||
|
client *pxapi.Client,
|
||||||
|
vmr *pxapi.VmRef,
|
||||||
|
systemPreProvision bool,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
if d.Get("preprovision").(bool) {
|
||||||
|
log.Print("[DEBUG] setting up SSH forward")
|
||||||
|
sshPort, err := pxapi.SshForwardUsernet(vmr, client)
|
||||||
|
if err != nil {
|
||||||
|
pmParallelEnd(pconf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done with proxmox API, end parallel and do the SSH things
|
||||||
|
pmParallelEnd(pconf)
|
||||||
|
|
||||||
|
d.SetConnInfo(map[string]string{
|
||||||
|
"type": "ssh",
|
||||||
|
"host": d.Get("ssh_forward_ip").(string),
|
||||||
|
"port": sshPort,
|
||||||
|
"user": d.Get("ssh_user").(string),
|
||||||
|
"private_key": d.Get("ssh_private_key").(string),
|
||||||
|
"pm_api_url": client.ApiUrl,
|
||||||
|
"pm_user": client.Username,
|
||||||
|
"pm_password": client.Password,
|
||||||
|
})
|
||||||
|
|
||||||
|
if systemPreProvision {
|
||||||
|
switch d.Get("os_type").(string) {
|
||||||
|
|
||||||
|
case "ubuntu":
|
||||||
|
// give sometime to bootup
|
||||||
|
time.Sleep(9 * time.Second)
|
||||||
|
err = preProvisionUbuntu(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case "centos":
|
||||||
|
// give sometime to bootup
|
||||||
|
time.Sleep(9 * time.Second)
|
||||||
|
err = preProvisionCentos(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown os_type: %s", d.Get("os_type").(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user