Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more flexible ipconfig setup #962

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions docs/resources/vm_qemu.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ parameter to create based
on [a Cloud-init configuration file](https://cloudinit.readthedocs.io/en/latest/topics/examples.html) or use the Proxmox
variable `ciuser`, `cipassword`, `ipconfig0`, `ipconfig1`, `ipconfig2`, `ipconfig3`, `ipconfig4`, `ipconfig5`,
`ipconfig6`, `ipconfig7`, `ipconfig8`, `ipconfig9`, `ipconfig10`, `ipconfig11`, `ipconfig12`, `ipconfig13`,
`ipconfig14`,`ipconfig15`, `searchdomain`, `nameserver` and `sshkeys`.
`ipconfig14`,`ipconfig15`, `searchdomain`, `nameserver` and `sshkeys`. A variable amount of static IPs can be configured using the dynamic [`ipconfig` block](#ipconfig-block) to list multiple IP addresses.

For more information, see the [Cloud-init guide](../guides/cloud_init.md).

Expand Down Expand Up @@ -165,13 +165,19 @@ details.
| Argument | Type | Default Value | Description |
| ----------- | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | `str` | | **Required** Network Card Model. The virtio model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use e1000. Options: `e1000`, `e1000-82540em`, `e1000-82544gc`, `e1000-82545em`, `i82551`, `i82557b`, `i82559er`, `ne2k_isa`, `ne2k_pci`, `pcnet`, `rtl8139`, `virtio`, `vmxnet3`. |
| `macaddr` | `str` | | Override the randomly generated MAC Address for the VM. Requires the MAC Address be Unicast. |
| `bridge` | `str` | `"nat"` | Bridge to which the network device should be attached. The Proxmox VE standard bridge is called `vmbr0`. |
| `tag` | `int` | `-1` | The VLAN tag to apply to packets on this device. `-1` disables VLAN tagging. |
| `firewall` | `bool` | `false` | Whether to enable the Proxmox firewall on this network device. |
| `rate` | `int` | `0` | Network device rate limit in mbps (megabytes per second) as floating point number. Set to `0` to disable rate limiting. |
| `queues` | `int` | `1` | Number of packet queues to be used on the device. Requires `virtio` model to have an effect. |
| `link_down` | `bool` | `false` | Whether this interface should be disconnected (like pulling the plug). |
| `macaddr` | `str` | | Override the randomly generated MAC Address for the VM. Requires the MAC Address be Unicast. |
| `bridge` | `str` | `"nat"` | Bridge to which the network device should be attached. The Proxmox VE standard bridge is called `vmbr0`. |
| `tag` | `int` | `-1` | The VLAN tag to apply to packets on this device. `-1` disables VLAN tagging. |
| `firewall` | `bool` | `false` | Whether to enable the Proxmox firewall on this network device. |
| `rate` | `int` | `0` | Network device rate limit in mbps (megabytes per second) as floating point number. Set to `0` to disable rate limiting. |
| `queues` | `int` | `1` | Number of packet queues to be used on the device. Requires `virtio` model to have an effect. |
| `link_down` | `bool` | `false` | Whether this interface should be disconnected (like pulling the plug). |

### Ipconfig Block

The `ipconfig` block is used to configure multiple static IP addresses. It may be specified multiple times.
| Argument | Type | Default Value | Description |
| `config` | `str` | | IP address to assign to the guest. Format: [gw=<GatewayIPv4>] [,gw6=<GatewayIPv6>] [,ip=<IPv4Format/CIDR>] [,ip6=<IPv6Format/CIDR>]. |

### Disks Block

Expand Down
4 changes: 0 additions & 4 deletions proxmox/resource_lxc.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,10 +573,6 @@ func resourceLxcCreate(d *schema.ResourceData, meta interface{}) error {
config.RootFs["size"] = config_post_resize.RootFs["size"]
config.RootFs["volume"] = config_post_resize.RootFs["volume"]

if err != nil {
return err
}

// Update all remaining stuff
err = config.UpdateConfig(vmr, client)
if err != nil {
Expand Down
118 changes: 82 additions & 36 deletions proxmox/resource_vm_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,15 @@ func resourceVmQemu() *schema.Resource {
return strings.TrimSpace(old) == strings.TrimSpace(new)
},
},
"ipconfig": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "ipconfig string in format: [gw=<GatewayIPv4>] [,gw6=<GatewayIPv6>] [,ip=<IPv4Format/CIDR>] [,ip6=<IPv6Format/CIDR>]",
},
},
"ipconfig0": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -816,6 +825,19 @@ func resourceVmQemuCreate(ctx context.Context, d *schema.ResourceData, meta inte

qemuUsbs, _ := ExpandDevicesList(d.Get("usb").([]interface{}))

// Populate Ipconfig map from iterable variable
qemuIpconfig, _ := ExpandIpconfigList(d.Get("ipconfig").([]interface{}))
// keeping this for backwards compatibility
if len(qemuIpconfig) == 0 {
// Populate Ipconfig map from explicit vars
for i := 0; i < 16; i++ {
iface := fmt.Sprintf("ipconfig%d", i)
if v, ok := d.GetOk(iface); ok {
qemuIpconfig[i] = v.(string)
}
}
}

config := pxapi.ConfigQemu{
Name: vmName,
Description: d.Get("desc").(string),
Expand Down Expand Up @@ -855,14 +877,7 @@ func resourceVmQemuCreate(ctx context.Context, d *schema.ResourceData, meta inte
Searchdomain: d.Get("searchdomain").(string),
Nameserver: d.Get("nameserver").(string),
Sshkeys: d.Get("sshkeys").(string),
Ipconfig: pxapi.IpconfigMap{},
}
// Populate Ipconfig map
for i := 0; i < 16; i++ {
iface := fmt.Sprintf("ipconfig%d", i)
if v, ok := d.GetOk(iface); ok {
config.Ipconfig[i] = v.(string)
}
Ipconfig: qemuIpconfig,
}

config.Disks = mapToStruct_QemuStorages(d)
Expand Down Expand Up @@ -1085,6 +1100,22 @@ func resourceVmQemuUpdate(ctx context.Context, d *schema.ResourceData, meta inte
return diag.FromErr(fmt.Errorf("error while processing Usb configuration: %v", err))
}

// Populate Ipconfig map from iterable variable
qemuIpconfig, err := ExpandIpconfigList(d.Get("ipconfig").([]interface{}))
if err != nil {
return diag.FromErr(fmt.Errorf("error while processing ipconfig configuration: %v", err))
}
// keeping this for backwards compatibility
if len(qemuIpconfig) == 0 {
// Populate Ipconfig map from explicit vars
for i := 0; i < 16; i++ {
iface := fmt.Sprintf("ipconfig%d", i)
if v, ok := d.GetOk(iface); ok {
qemuIpconfig[i] = v.(string)
}
}
}

d.Partial(true)
if d.HasChange("target_node") {
// Update target node when it must be migrated manually. Don't if it has been migrated by the proxmox high availability system.
Expand Down Expand Up @@ -1131,24 +1162,7 @@ func resourceVmQemuUpdate(ctx context.Context, d *schema.ResourceData, meta inte
Searchdomain: d.Get("searchdomain").(string),
Nameserver: d.Get("nameserver").(string),
Sshkeys: d.Get("sshkeys").(string),
Ipconfig: pxapi.IpconfigMap{
0: d.Get("ipconfig0").(string),
1: d.Get("ipconfig1").(string),
2: d.Get("ipconfig2").(string),
3: d.Get("ipconfig3").(string),
4: d.Get("ipconfig4").(string),
5: d.Get("ipconfig5").(string),
6: d.Get("ipconfig6").(string),
7: d.Get("ipconfig7").(string),
8: d.Get("ipconfig8").(string),
9: d.Get("ipconfig9").(string),
10: d.Get("ipconfig10").(string),
11: d.Get("ipconfig11").(string),
12: d.Get("ipconfig12").(string),
13: d.Get("ipconfig13").(string),
14: d.Get("ipconfig14").(string),
15: d.Get("ipconfig15").(string),
},
Ipconfig: qemuIpconfig,
}
if len(qemuVgaList) > 0 {
config.QemuVga = qemuVgaList[0].(map[string]interface{})
Expand Down Expand Up @@ -1187,6 +1201,7 @@ func resourceVmQemuUpdate(ctx context.Context, d *schema.ResourceData, meta inte
"searchdomain",
"nameserver",
"sshkeys",
"ipconfig",
"ipconfig0",
"ipconfig1",
"ipconfig2",
Expand Down Expand Up @@ -1436,6 +1451,7 @@ func resourceVmQemuRead(ctx context.Context, d *schema.ResourceData, meta interf
d.Set("searchdomain", config.Searchdomain)
d.Set("nameserver", config.Nameserver)
d.Set("sshkeys", config.Sshkeys)
d.Set("ipconfig", config.Ipconfig)
d.Set("ipconfig0", config.Ipconfig[0])
d.Set("ipconfig1", config.Ipconfig[1])
d.Set("ipconfig2", config.Ipconfig[2])
Expand Down Expand Up @@ -1476,21 +1492,21 @@ func resourceVmQemuRead(ctx context.Context, d *schema.ResourceData, meta interf
// read in the qemu hostpci
qemuPCIDevices, _ := FlattenDevicesList(config.QemuPCIDevices)
logger.Debug().Int("vmid", vmID).Msgf("Hostpci Block Processed '%v'", config.QemuPCIDevices)
if d.Set("hostpci", qemuPCIDevices); err != nil {
if err = d.Set("hostpci", qemuPCIDevices); err != nil {
return diag.FromErr(err)
}

// read in the qemu hostpci
qemuUsbsDevices, _ := FlattenDevicesList(config.QemuUsbs)
logger.Debug().Int("vmid", vmID).Msgf("Usb Block Processed '%v'", config.QemuUsbs)
if d.Set("usb", qemuUsbsDevices); err != nil {
if err = d.Set("usb", qemuUsbsDevices); err != nil {
return diag.FromErr(err)
}

// read in the unused disks
flatUnusedDisks, _ := FlattenDevicesList(config.QemuUnusedDisks)
logger.Debug().Int("vmid", vmID).Msgf("Unused Disk Block Processed '%v'", config.QemuUnusedDisks)
if d.Set("unused_disk", flatUnusedDisks); err != nil {
if err = d.Set("unused_disk", flatUnusedDisks); err != nil {
return diag.FromErr(err)
}

Expand Down Expand Up @@ -1754,6 +1770,34 @@ func ExpandDevicesList(deviceList []interface{}) (pxapi.QemuDevices, error) {
return expandedDevices, nil
}

// Consumes a terraform TypeList of a Qemu Device (network, hard drive, etc) and returns the "Expanded"
// version of the equivalent configuration that the API understands (the struct pxapi.IpconfigMap).
func ExpandIpconfigList(ipconfigList []interface{}) (pxapi.IpconfigMap, error) {
expandedDevices := make(pxapi.IpconfigMap)

if len(ipconfigList) == 0 {
return expandedDevices, nil
}

ipconfigDevices, err := ExpandDevicesList(ipconfigList)
if err != nil {
return expandedDevices, err
}

for index, thisDeviceMap := range ipconfigDevices {
// thisDeviceMap := deviceInterface

// bail out if the device is empty, it is meaningless in this context
if thisDeviceMap == nil {
continue
}

expandedDevices[index] = thisDeviceMap["config"]
}

return expandedDevices, nil
}

// Update schema.TypeSet with new values comes from Proxmox API.
// TODO: remove these set functions and convert attributes using a set to a list instead.
func UpdateDevicesSet(
Expand Down Expand Up @@ -1908,8 +1952,8 @@ func initConnInfo(ctx context.Context,
if config.HasCloudInit() {
log.Print("[DEBUG][initConnInfo] vm has a cloud-init configuration")
logger.Debug().Int("vmid", vmr.VmId()).Msgf(" vm has a cloud-init configuration")
_, ipconfig0Set := d.GetOk("ipconfig0")
if ipconfig0Set {
_, ipconfigSet := d.GetOk("ipconfig")
if ipconfigSet {
vmState, err := client.GetVmState(vmr)
log.Printf("[DEBUG][initConnInfo] cloudinitcheck vm state %v", vmState)
logger.Debug().Int("vmid", vmr.VmId()).Msgf("cloudinitcheck vm state %v", vmState)
Expand All @@ -1918,16 +1962,18 @@ func initConnInfo(ctx context.Context,
logger.Debug().Int("vmid", vmr.VmId()).Msgf("vmstate error %s", err.Error())
return diag.FromErr(err)
}
if d.Get("ipconfig0").(string) != "ip=dhcp" || vmState["agent"] == nil || vmState["agent"].(float64) != 1 {
// parse IP address out of ipconfig0
ipMatch := rxIPconfig.FindStringSubmatch(d.Get("ipconfig0").(string))

ipConfig := d.Get("ipconfig").(map[int]string)
if ipConfig[0] != "ip=dhcp" || vmState["agent"] == nil || vmState["agent"].(float64) != 1 {
// parse IP address out of ipconfig
ipMatch := rxIPconfig.FindStringSubmatch(ipConfig[0])
if sshHost == "" {
sshHost = ipMatch[1]
}
ipconfig0 := net.ParseIP(strings.Split(ipMatch[1], ":")[0])
interfaces, err = client.GetVmAgentNetworkInterfaces(vmr)
log.Printf("[DEBUG][initConnInfo] ipconfig0 interfaces: %v", interfaces)
logger.Debug().Int("vmid", vmr.VmId()).Msgf("ipconfig0 interfaces %v", interfaces)
log.Printf("[DEBUG][initConnInfo] ipconfig[0] interfaces: %v", interfaces)
logger.Debug().Int("vmid", vmr.VmId()).Msgf("ipconfig[0] interfaces %v", interfaces)
if err != nil {
return diag.FromErr(err)
} else {
Expand Down
Loading