Skip to content

Commit

Permalink
Merge branch 'Telmate:master' into Telmate#1106
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinyblargon authored Oct 31, 2024
2 parents f696132 + 8e4bad1 commit 9db77c8
Show file tree
Hide file tree
Showing 15 changed files with 465 additions and 148 deletions.
131 changes: 131 additions & 0 deletions docs/guides/cloud-init getting started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Cloud-Init Getting Started

This guide will help you get started with Cloud-Init on Proxmox Virtual Environment `PVE`. Cloud Init is a multi-distribution package that handles early initialization of a virtual machine. It is used for configuring the hostname, setting up SSH keys, and other tasks that need to be done before the virtual machine is ready for use.

Note: **all command are performed from the PVE shell**.

## Creating a Cloud Init Template

Before you can use Cloud-Init, you need to create a template that will be used to clone new virtual machines. This template will have the Cloud-Init package installed and configured. The following steps will guide you through creating a Cloud Init template:

### Downloading a Cloud-Init Image

For this guide, we will use the Debian 12 Cloud-Init image. You can download the image from the following link:

```bash
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
```

### Importing the Cloud-Init Image

Before we can import the Cloud-Init image, we need to create a VM to give the image to. The following command will create a new VM with the ID `9000`:

```bash
qm create 9000 --name debian12-cloudinit
```

Note: **Terraform is meant to manage the full life cycle of the VM, therefore we won't make any further changes to the VM**.

Once the VM is created, we can import the Cloud-Init image using the following command:

```bash
qm set 9000 --scsi0 local-lvm:0,import-from=/root/debian-12-genericcloud-amd64.qcow2
```

### Creating a Template from the VM

Now that we have the Cloud-Init image imported, we can create a template from the VM. The following command will convert the VM with ID `9000` to a template:

```bash
qm template 9000
```

## Creating a Snippet

Snippets are used to pass additional configuration to the Cloud-Init package. For this guide we will create a snippet that ensures the `qemu-guest-agent` package is installed on the virtual machine. Before we can create a snippet, we need to create a place to store it. Preferably in the same storage as the template. Do keep in mind that the cloned VMs can't start if the snippet is not accessible. Throughout this guide we will use the `local` storage.

```bash
mkdir /var/lib/vz/snippets
```

Now that we have a place to store the snippet, we can create the snippet itself. The following command will create a snippet that installs the `qemu-guest-agent.yml` package:

```bash
tee /var/lib/vz/snippets/qemu-guest-agent.yml <<EOF
#cloud-config
runcmd:
- apt update
- apt install -y qemu-guest-agent
- systemctl start qemu-guest-agent
EOF
```

## Terraform Configuration

Now that we have a Cloud-Init template and a snippet, we can use Terraform to create a new VM from the template. The following Terraform configuration will create a new VM with the ID `100`:

```hcl
resource "proxmox_vm_qemu" "cloudinit-example" {
vmid = 100
name = "test-terraform0"
target_node = "pve"
agent = 1
cores = 2
memory = 1024
boot = "order=scsi0" # has to be the same as the OS disk of the template
clone = "debian12-cloudinit" # The name of the template
scsihw = "virtio-scsi-single"
vm_state = "running"
automatic_reboot = true
# Cloud-Init configuration
cicustom = "vendor=local:snippets/qemu-guest-agent.yml" # /var/lib/vz/snippets/qemu-guest-agent.yml
ciupgrade = true
nameserver = "1.1.1.1 8.8.8.8"
ipconfig0 = "ip=192.168.1.10/24,gw=192.168.1.1,ip6=dhcp"
skip_ipv6 = true
ciuser = "root"
cipassword = "Enter123!"
sshkeys = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/Pjg7YXZ8Yau9heCc4YWxFlzhThnI+IhUx2hLJRxYE Cloud-Init@Terraform"
# Most cloud-init images require a serial device for their display
serial {
id = 0
}
disks {
scsi {
scsi0 {
# We have to specify the disk from our template, else Terraform will think it's not supposed to be there
disk {
storage = "local-lvm"
# The size of the disk should be at least as big as the disk in the template. If it's smaller, the disk will be recreated
size = "2G"
}
}
}
ide {
# Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller
ide1 {
cloudinit {
storage = "local-lvm"
}
}
}
}
network {
bridge = "vmbr0"
model = "virtio"
}
}
terraform {
required_providers {
proxmox = {
source = "Telmate/proxmox"
version = ">=3.0.1rc4"
}
}
}
```
6 changes: 4 additions & 2 deletions docs/resources/vm_qemu.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ resource "proxmox_vm_qemu" "pxe-minimal-example" {
pxe = true
target_node = "test"
network {
id = 0
bridge = "vmbr0"
firewall = false
link_down = false
Expand Down Expand Up @@ -165,8 +166,9 @@ second will be `net1` etc...
See the [docs about network devices](https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_network_device) for more
details.

| Argument | Type | Default Value | Description |
| ----------- | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Argument | Type | Default Value | Description |
| ----------- | ------ | ------------- | ----------- |
| `id` | `int` | | **Required** The ID of the network device `0`-`31`. |
| `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`. |
Expand Down
1 change: 1 addition & 0 deletions examples/cloudinit_example.tf
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ resource "proxmox_vm_qemu" "cloudinit-test" {

# Setup the network interface and assign a vlan tag: 256
network {
id = 0
model = "virtio"
bridge = "vmbr0"
tag = 256
Expand Down
1 change: 1 addition & 0 deletions examples/pxe_example.tf
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ resource "proxmox_vm_qemu" "pxe-example" {
}

network {
id = 0
bridge = "vmbr0"
firewall = false
link_down = false
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
toolchain go1.21.0

require (
github.com/Telmate/proxmox-api-go v0.0.0-20240827160542-0d787afdba05
github.com/Telmate/proxmox-api-go v0.0.0-20241028065640-b38644dd6993
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton h1:HKz85FwoXx86kVtTvFke7rgHvq/HoloSUvW5semjFWs=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/Telmate/proxmox-api-go v0.0.0-20240827160542-0d787afdba05 h1:yYVjf1Bp6qYHFuOlV8eNwUh1kpYMnUFrop4iLvY+/ZY=
github.com/Telmate/proxmox-api-go v0.0.0-20240827160542-0d787afdba05/go.mod h1:Gu6n6vEn1hlyFUkjrvU+X1fdgaSXLoM9HKYYJqy1fsY=
github.com/Telmate/proxmox-api-go v0.0.0-20241028065640-b38644dd6993 h1:HOkpCRbJXYt/dFecrbp6rs7hVaJjADHMXDVA07Kpewg=
github.com/Telmate/proxmox-api-go v0.0.0-20241028065640-b38644dd6993/go.mod h1:Gu6n6vEn1hlyFUkjrvU+X1fdgaSXLoM9HKYYJqy1fsY=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
Expand Down
141 changes: 141 additions & 0 deletions proxmox/Internal/resource/guest/qemu/network/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package network

import (
"net"

pxapi "github.com/Telmate/proxmox-api-go/proxmox"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
Root string = "network"

maximumNetworkInterfaces int = int(pxapi.QemuNetworkInterfacesAmount)

schemaID string = "id"

schemaBridge string = "bridge"
schemaFirewall string = "firewall"
schemaLinkDown string = "link_down"
schemaMAC string = "macaddr"
schemaMTU string = "mtu"
schemaModel string = "model"
schemaNativeVlan string = "tag"
schemaQueues string = "queues"
schemaRate string = "rate"
)

func Schema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: maximumNetworkInterfaces,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
schemaID: {
Type: schema.TypeInt,
Required: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be in the range 0 - %d, got: %d", schemaID, pxapi.QemuNetworkInterfaceIDMaximum, v)
}
err := pxapi.QemuNetworkInterfaceID(v).Validate()
if err != nil {
return diag.Errorf("%s must be in the range 0 - %d, got: %d", schemaID, pxapi.QemuNetworkInterfaceIDMaximum, v)
}
return nil
}},
schemaBridge: {
Type: schema.TypeString,
Optional: true,
Default: "nat"},
schemaFirewall: {
Type: schema.TypeBool,
Optional: true,
Default: false},
schemaLinkDown: {
Type: schema.TypeBool,
Optional: true,
Default: false},
schemaMAC: {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
oldMAC, _ := net.ParseMAC(old)
newMAC, _ := net.ParseMAC(new)
return oldMAC.String() == newMAC.String()
},
ValidateDiagFunc: func(i interface{}, p cty.Path) diag.Diagnostics {
v := i.(string)
if _, err := net.ParseMAC(v); err != nil {
return diag.Errorf("invalid %s: %s", schemaMAC, v)
}
return nil
}},
schemaMTU: {
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v == 1 {
return nil
}
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaMTU, v)
}
if err := pxapi.MTU(v).Validate(); err != nil {
return diag.Errorf("%s must be in the range 576 - 65520, or 1 got: %d", schemaMTU, v)
}
return nil
}},
schemaModel: {
Type: schema.TypeString,
Required: true,
ForceNew: false,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(string)
if err := pxapi.QemuNetworkModel(v).Validate(); err != nil {
return diag.Errorf("invalid network %s: %s", schemaModel, v)
}
return nil
}},
schemaNativeVlan: {
Type: schema.TypeInt,
Optional: true,
Description: "VLAN tag.",
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaNativeVlan, v)
}
return nil
}},
schemaQueues: {
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaQueues, v)
}
if err := pxapi.QemuNetworkQueue(v).Validate(); err != nil {
return diag.Errorf("%s must be in the range 0 - %d, got: %d", schemaQueues, pxapi.QemuNetworkQueueMaximum, v)
}
return nil
}},
schemaRate: {
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaRate, v)
}
return nil
}}}}}
}
61 changes: 61 additions & 0 deletions proxmox/Internal/resource/guest/qemu/network/sdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package network

import (
"fmt"
"net"
"strconv"
"strings"

pxapi "github.com/Telmate/proxmox-api-go/proxmox"
"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// Converts the Terraform configuration to the SDK configuration
func SDK(d *schema.ResourceData) (pxapi.QemuNetworkInterfaces, diag.Diagnostics) {
networks := make(pxapi.QemuNetworkInterfaces, maximumNetworkInterfaces)
for i := 0; i < maximumNetworkInterfaces; i++ {
networks[pxapi.QemuNetworkInterfaceID(i)] = pxapi.QemuNetworkInterface{Delete: true}
}
var diags diag.Diagnostics
for _, e := range d.Get(Root).([]interface{}) {
networkMap := e.(map[string]interface{})
id := pxapi.QemuNetworkInterfaceID(networkMap[schemaID].(int))
if v, duplicate := networks[id]; duplicate {
if !v.Delete {
diags = append(diags, diag.Errorf("Duplicate network interface %s %d", schemaID, id)...)
}
}
tmpMAC, _ := net.ParseMAC(networkMap[schemaMAC].(string))
mtu := networkMap[schemaMTU].(int)
var tmpMTU pxapi.QemuMTU
model := pxapi.QemuNetworkModel(networkMap[schemaModel].(string))
if mtu != 0 {
if string(pxapi.QemuNetworkModelVirtIO) == model.String() {
if mtu == 1 {
tmpMTU = pxapi.QemuMTU{Inherit: false}
} else {
tmpMTU = pxapi.QemuMTU{Value: pxapi.MTU(mtu)}
}
} else {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("%s is only supported when model is %s", schemaMTU, pxapi.QemuNetworkModelVirtIO)})
}
}
rateString, _, _ := strings.Cut(strconv.Itoa(networkMap[schemaRate].(int)), ".")
rate, _ := strconv.ParseInt(rateString, 10, 64)
networks[id] = pxapi.QemuNetworkInterface{
Bridge: util.Pointer(networkMap[schemaBridge].(string)),
Connected: util.Pointer(!networkMap[schemaLinkDown].(bool)),
Delete: false,
Firewall: util.Pointer(networkMap[schemaFirewall].(bool)),
MAC: &tmpMAC,
MTU: &tmpMTU,
Model: util.Pointer(model),
MultiQueue: util.Pointer(pxapi.QemuNetworkQueue(networkMap[schemaQueues].(int))),
RateLimitKBps: util.Pointer(pxapi.QemuNetworkRate(rate))}
}
return networks, diags
}
Loading

0 comments on commit 9db77c8

Please sign in to comment.