forked from Telmate/terraform-provider-proxmox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'Telmate:master' into Telmate#1106
- Loading branch information
Showing
15 changed files
with
465 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}}}}} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.