mirror of
https://github.com/letic/terraform-provider-proxmox.git
synced 2024-10-03 01:01:05 +00:00
untested cloud-init support
This commit is contained in:
parent
9291c20a84
commit
e83e6af1d4
61
README.md
61
README.md
@ -34,7 +34,45 @@ provider "proxmox" {
|
||||
pm_tls_insecure = true
|
||||
}
|
||||
|
||||
resource "proxmox_vm_qemu" "test" {
|
||||
/* Uses cloud-init options from Proxmox 5.2 */
|
||||
resource "proxmox_vm_qemu" "cloudinit-test" {
|
||||
name = "tftest1.xyz.com"
|
||||
desc = "tf description"
|
||||
target_node = "proxmox1-xx"
|
||||
|
||||
clone = "ci-ubuntu-template"
|
||||
storage = "local"
|
||||
cores = 3
|
||||
sockets = 1
|
||||
memory = 2560
|
||||
disk_gb = 4
|
||||
nic = "virtio"
|
||||
bridge = "vmbr0"
|
||||
|
||||
ssh_user = "root"
|
||||
ssh_private_key = <<EOF
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
private ssh key root
|
||||
-----END RSA PRIVATE KEY-----
|
||||
EOF
|
||||
|
||||
os_type = "cloud-init"
|
||||
ipconfig0 = "ip=10.0.2.99, gw=10.0.2.2"
|
||||
|
||||
sshkeys = <<EOF
|
||||
ssh-rsa AAAAB3NzaC1kj...key1
|
||||
ssh-rsa AAAAB3NzaC1kj...key2
|
||||
EOF
|
||||
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"ip a"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/* Uses custom eth1 user-net SSH portforward */
|
||||
resource "proxmox_vm_qemu" "prepprovision-test" {
|
||||
name = "tftest1.xyz.com"
|
||||
desc = "tf description"
|
||||
target_node = "proxmox1-xx"
|
||||
@ -80,12 +118,27 @@ Optimally, you could create a VM resource you will use a clone base with an ISO,
|
||||
|
||||
Interesting parameters:
|
||||
|
||||
**ssh_forward_ip** - should be the IP or hostname of the target node or bridge IP. This is where proxmox will create a port forward to your VM with via a user_net.
|
||||
**os_type** -
|
||||
* cloud-init - from Proxmox 5.2
|
||||
* ubuntu -(https://github.com/Telmate/terraform-ubuntu-proxmox-iso)
|
||||
* centos - (TODO: centos iso template)
|
||||
|
||||
**os_type** - ubuntu (https://github.com/Telmate/terraform-ubuntu-proxmox-iso) or centos (TODO: centos iso template)
|
||||
**ssh_forward_ip** - should be the IP or hostname of the target node or bridge IP. This is where proxmox will create a port forward to your VM with via a user_net. (for pre-cloud-init provisioning)
|
||||
|
||||
### Cloud-Init
|
||||
|
||||
### Preprovision (internal)
|
||||
Cloud-init VMs must be cloned from a cloud-init ready template.
|
||||
See: https://pve.proxmox.com/wiki/Cloud-Init_Support
|
||||
|
||||
* ciuser - User name to change ssh keys and password for instead of the image’s configured default user.
|
||||
* cipassword - Password to assign the user.
|
||||
* searchdomain - Sets DNS search domains for a container.
|
||||
* nameserver - Sets DNS server IP address for a container.
|
||||
* sshkeys - public ssh keys, one per line
|
||||
* ipconfig0 - [gw=<GatewayIPv4>] [,gw6=<GatewayIPv6>] [,ip=<IPv4Format/CIDR>] [,ip6=<IPv6Format/CIDR>]
|
||||
* ipconfig1 - optional, same as ipconfig0 format
|
||||
|
||||
### Preprovision (internal alternative to Cloud-Init)
|
||||
|
||||
There is a pre-provision phase which is used to set a hostname, intialize eth0, and resize the VM disk to available space. This is done over SSH with the ssh_forward_ip, ssh_user and ssh_private_key.
|
||||
|
||||
|
@ -15,11 +15,12 @@ type providerConfiguration struct {
|
||||
Client *pxapi.Client
|
||||
MaxParallel int
|
||||
CurrentParallel int
|
||||
MaxVmId int
|
||||
MaxVMID int
|
||||
Mutex *sync.Mutex
|
||||
Cond *sync.Cond
|
||||
}
|
||||
|
||||
// Provider - Terrafrom properties for proxmox
|
||||
func Provider() *schema.Provider {
|
||||
return &schema.Provider{
|
||||
|
||||
@ -76,7 +77,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||
Client: client,
|
||||
MaxParallel: d.Get("pm_parallel").(int),
|
||||
CurrentParallel: 0,
|
||||
MaxVmId: -1,
|
||||
MaxVMID: -1,
|
||||
Mutex: &mut,
|
||||
Cond: sync.NewCond(&mut),
|
||||
}, nil
|
||||
@ -97,11 +98,11 @@ func getClient(pm_api_url string, pm_user string, pm_password string, pm_tls_ins
|
||||
|
||||
func nextVmId(pconf *providerConfiguration) (nextId int, err error) {
|
||||
pconf.Mutex.Lock()
|
||||
pconf.MaxVmId, err = pconf.Client.GetNextID(pconf.MaxVmId + 1)
|
||||
pconf.MaxVMID, err = pconf.Client.GetNextID(pconf.MaxVMID + 1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nextId = pconf.MaxVmId
|
||||
nextId = pconf.MaxVMID
|
||||
pconf.Mutex.Unlock()
|
||||
return nextId, nil
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// Provisioner - Terrafrom properties for proxmox-provisioner
|
||||
func Provisioner() terraform.ResourceProvisioner {
|
||||
return &schema.Provisioner{
|
||||
Schema: map[string]*schema.Schema{
|
||||
@ -27,7 +28,7 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||
}
|
||||
}
|
||||
|
||||
var currentClient *pxapi.Client = nil
|
||||
var currentClient *pxapi.Client
|
||||
|
||||
func applyFn(ctx context.Context) error {
|
||||
data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
||||
@ -36,11 +37,11 @@ func applyFn(ctx context.Context) error {
|
||||
connInfo := state.Ephemeral.ConnInfo
|
||||
|
||||
act := data.Get("action").(string)
|
||||
targetNode, _, vmId, err := parseResourceId(state.ID)
|
||||
targetNode, _, vmID, err := parseResourceId(state.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vmr := pxapi.NewVmRef(vmId)
|
||||
vmr := pxapi.NewVmRef(vmID)
|
||||
vmr.SetNode(targetNode)
|
||||
client := currentClient
|
||||
if client == nil {
|
||||
@ -69,5 +70,4 @@ func applyFn(ctx context.Context) error {
|
||||
default:
|
||||
return fmt.Errorf("Unkown action: %s", act)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -38,6 +39,7 @@ func resourceVmQemu() *schema.Resource {
|
||||
"target_node": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"ssh_forward_ip": {
|
||||
Type: schema.TypeString,
|
||||
@ -46,15 +48,22 @@ func resourceVmQemu() *schema.Resource {
|
||||
"iso": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"clone": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"storage": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"storage_type": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"qemu_os": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
@ -119,16 +128,61 @@ func resourceVmQemu() *schema.Resource {
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
"mac": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if old == "" {
|
||||
return false // macaddr auto-generates and its ok
|
||||
}
|
||||
return strings.TrimSpace(old) == strings.TrimSpace(new)
|
||||
},
|
||||
},
|
||||
"ci_wait": { // how long to wait before provision
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 30,
|
||||
},
|
||||
"ciuser": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"cipassword": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"searchdomain": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"nameserver": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"sshkeys": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"ipconfig0": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"ipconfig1": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var rxIPconfig = regexp.MustCompile("ip6?=([0-9a-fA-F:\\.]+)")
|
||||
|
||||
func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
pconf := meta.(*providerConfiguration)
|
||||
pmParallelBegin(pconf)
|
||||
client := pconf.Client
|
||||
vmName := d.Get("name").(string)
|
||||
disk_gb := d.Get("disk_gb").(float64)
|
||||
diskGB := d.Get("disk_gb").(float64)
|
||||
config := pxapi.ConfigQemu{
|
||||
Name: vmName,
|
||||
Description: d.Get("desc").(string),
|
||||
@ -136,11 +190,19 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
Memory: d.Get("memory").(int),
|
||||
QemuCores: d.Get("cores").(int),
|
||||
QemuSockets: d.Get("sockets").(int),
|
||||
DiskSize: disk_gb,
|
||||
DiskSize: diskGB,
|
||||
QemuOs: d.Get("qemu_os").(string),
|
||||
QemuNicModel: d.Get("nic").(string),
|
||||
QemuBrige: d.Get("bridge").(string),
|
||||
QemuVlanTag: d.Get("vlan").(int),
|
||||
QemuMacAddr: d.Get("mac").(string),
|
||||
CIuser: d.Get("ciuser").(string),
|
||||
CIpassword: d.Get("cipassword").(string),
|
||||
Searchdomain: d.Get("searchdomain").(string),
|
||||
Nameserver: d.Get("nameserver").(string),
|
||||
Sshkeys: d.Get("sshkeys").(string),
|
||||
Ipconfig0: d.Get("ipconfig0").(string),
|
||||
Ipconfig1: d.Get("ipconfig1").(string),
|
||||
}
|
||||
log.Print("[DEBUG] checking for duplicate name")
|
||||
dupVmr, _ := client.GetVmRefByName(vmName)
|
||||
@ -185,7 +247,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
// give sometime to proxmox to catchup
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
err = prepareDiskSize(client, vmr, disk_gb)
|
||||
err = prepareDiskSize(client, vmr, diskGB)
|
||||
if err != nil {
|
||||
pmParallelEnd(pconf)
|
||||
return err
|
||||
@ -213,7 +275,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
// give sometime to proxmox to catchup
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
err = prepareDiskSize(client, vmr, disk_gb)
|
||||
err = prepareDiskSize(client, vmr, diskGB)
|
||||
if err != nil {
|
||||
pmParallelEnd(pconf)
|
||||
return err
|
||||
@ -230,19 +292,33 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
pmParallelEnd(pconf)
|
||||
return err
|
||||
}
|
||||
|
||||
sshPort := "22"
|
||||
sshHost := ""
|
||||
if config.HasCloudInit() {
|
||||
if d.Get("ssh_forward_ip") != nil {
|
||||
sshHost = d.Get("ssh_forward_ip").(string)
|
||||
} else {
|
||||
// parse IP address out of ipconfig0
|
||||
ipMatch := rxIPconfig.FindStringSubmatch(d.Get("ipconfig0").(string))
|
||||
sshHost = ipMatch[1]
|
||||
}
|
||||
} else {
|
||||
log.Print("[DEBUG] setting up SSH forward")
|
||||
sshPort, err := pxapi.SshForwardUsernet(vmr, client)
|
||||
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": d.Get("ssh_forward_ip").(string),
|
||||
"host": sshHost,
|
||||
"port": sshPort,
|
||||
"user": d.Get("ssh_user").(string),
|
||||
"private_key": d.Get("ssh_private_key").(string),
|
||||
@ -270,6 +346,10 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case "cloud-init":
|
||||
// wait for OS too boot awhile...
|
||||
time.Sleep(time.Duration(d.Get("ci_wait").(int)) * time.Second)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unknown os_type: %s", d.Get("os_type").(string))
|
||||
}
|
||||
@ -287,7 +367,7 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
return err
|
||||
}
|
||||
vmName := d.Get("name").(string)
|
||||
disk_gb := d.Get("disk_gb").(float64)
|
||||
diskGB := d.Get("disk_gb").(float64)
|
||||
config := pxapi.ConfigQemu{
|
||||
Name: vmName,
|
||||
Description: d.Get("desc").(string),
|
||||
@ -295,11 +375,19 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
Memory: d.Get("memory").(int),
|
||||
QemuCores: d.Get("cores").(int),
|
||||
QemuSockets: d.Get("sockets").(int),
|
||||
DiskSize: disk_gb,
|
||||
DiskSize: diskGB,
|
||||
QemuOs: d.Get("qemu_os").(string),
|
||||
QemuNicModel: d.Get("nic").(string),
|
||||
QemuBrige: d.Get("bridge").(string),
|
||||
QemuVlanTag: d.Get("vlan").(int),
|
||||
QemuMacAddr: d.Get("mac").(string),
|
||||
CIuser: d.Get("ciuser").(string),
|
||||
CIpassword: d.Get("cipassword").(string),
|
||||
Searchdomain: d.Get("searchdomain").(string),
|
||||
Nameserver: d.Get("nameserver").(string),
|
||||
Sshkeys: d.Get("sshkeys").(string),
|
||||
Ipconfig0: d.Get("ipconfig0").(string),
|
||||
Ipconfig1: d.Get("ipconfig1").(string),
|
||||
}
|
||||
|
||||
err = config.UpdateConfig(vmr, client)
|
||||
@ -311,7 +399,7 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
// give sometime to proxmox to catchup
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
prepareDiskSize(client, vmr, disk_gb)
|
||||
prepareDiskSize(client, vmr, diskGB)
|
||||
|
||||
// give sometime to proxmox to catchup
|
||||
time.Sleep(5 * time.Second)
|
||||
@ -361,6 +449,7 @@ func resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error {
|
||||
d.Set("name", config.Name)
|
||||
d.Set("desc", config.Description)
|
||||
d.Set("storage", config.Storage)
|
||||
d.Set("storage_type", config.StorageType)
|
||||
d.Set("memory", config.Memory)
|
||||
d.Set("cores", config.QemuCores)
|
||||
d.Set("sockets", config.QemuSockets)
|
||||
@ -369,6 +458,16 @@ func resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error {
|
||||
d.Set("nic", config.QemuNicModel)
|
||||
d.Set("bridge", config.QemuBrige)
|
||||
d.Set("vlan", config.QemuVlanTag)
|
||||
d.Set("mac", config.QemuMacAddr)
|
||||
|
||||
d.Set("ciuser", config.CIuser)
|
||||
d.Set("cipassword", config.CIpassword)
|
||||
d.Set("searchdomain", config.Searchdomain)
|
||||
d.Set("nameserver", config.Nameserver)
|
||||
d.Set("sshkeys", config.Sshkeys)
|
||||
d.Set("ipconfig0", config.Ipconfig0)
|
||||
d.Set("ipconfig1", config.Ipconfig1)
|
||||
|
||||
pmParallelEnd(pconf)
|
||||
return nil
|
||||
}
|
||||
@ -397,14 +496,14 @@ func resourceVmQemuDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func prepareDiskSize(client *pxapi.Client, vmr *pxapi.VmRef, disk_gb float64) error {
|
||||
func prepareDiskSize(client *pxapi.Client, vmr *pxapi.VmRef, diskGB float64) error {
|
||||
clonedConfig, err := pxapi.NewConfigQemuFromApi(vmr, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if disk_gb > clonedConfig.DiskSize {
|
||||
log.Print("[DEBUG] resizing disk")
|
||||
_, err = client.ResizeQemuDisk(vmr, "virtio0", int(disk_gb-clonedConfig.DiskSize))
|
||||
if diskGB > clonedConfig.DiskSize {
|
||||
log.Print("[DEBUG] resizing disk " + clonedConfig.StorageType)
|
||||
_, err = client.ResizeQemuDisk(vmr, clonedConfig.StorageType+"0", int(diskGB-clonedConfig.DiskSize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user