untested cloud-init support

This commit is contained in:
Grant Gongaware 2018-07-13 10:25:37 -07:00
parent 821ceb8d0d
commit 83722fe9b9
4 changed files with 182 additions and 29 deletions

View File

@ -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 images 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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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,11 +292,25 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
pmParallelEnd(pconf)
return err
}
log.Print("[DEBUG] setting up SSH forward")
sshPort, err := pxapi.SshForwardUsernet(vmr, client)
if err != nil {
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)
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
@ -242,7 +318,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error {
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
}