mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-08-22 19:38:35 +00:00
fet(vm): allow scsi
and sata
interfaces for CloudInit Drive (#598)
* fet(vm): allow `scsi` and `sata` interfaces for CloudInit Drive --------- Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
fa590ed9e9
commit
0b8f2e2c6f
@ -300,8 +300,9 @@ output "ubuntu_vm_public_key" {
|
|||||||
- `datastore_id` - (Optional) The identifier for the datastore to create the
|
- `datastore_id` - (Optional) The identifier for the datastore to create the
|
||||||
cloud-init disk in (defaults to `local-lvm`).
|
cloud-init disk in (defaults to `local-lvm`).
|
||||||
- `interface` - (Optional) The hardware interface to connect the cloud-init
|
- `interface` - (Optional) The hardware interface to connect the cloud-init
|
||||||
image to. Must be `ideN`. Will be detected if the setting is missing but a
|
image to. Must be one of `ide0..3`, `sata0..5`, `scsi0..30`. Will be
|
||||||
cloud-init image is present, otherwise defaults to `ide2`.
|
detected if the setting is missing but a cloud-init image is present,
|
||||||
|
otherwise defaults to `ide2`.
|
||||||
- `dns` - (Optional) The DNS configuration.
|
- `dns` - (Optional) The DNS configuration.
|
||||||
- `domain` - (Optional) The DNS search domain.
|
- `domain` - (Optional) The DNS search domain.
|
||||||
- `server` - (Optional) The DNS server.
|
- `server` - (Optional) The DNS server.
|
||||||
|
@ -7,6 +7,7 @@ resource "proxmox_virtual_environment_vm" "example_template" {
|
|||||||
enabled = true
|
enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bios = "ovmf"
|
||||||
description = "Managed by Terraform"
|
description = "Managed by Terraform"
|
||||||
|
|
||||||
cpu {
|
cpu {
|
||||||
@ -56,7 +57,7 @@ resource "proxmox_virtual_environment_vm" "example_template" {
|
|||||||
|
|
||||||
initialization {
|
initialization {
|
||||||
datastore_id = local.datastore_id
|
datastore_id = local.datastore_id
|
||||||
# interface = "ide2"
|
interface = "scsi4"
|
||||||
|
|
||||||
dns {
|
dns {
|
||||||
server = "1.1.1.1"
|
server = "1.1.1.1"
|
||||||
@ -76,7 +77,8 @@ resource "proxmox_virtual_environment_vm" "example_template" {
|
|||||||
meta_data_file_id = proxmox_virtual_environment_file.meta_config.id
|
meta_data_file_id = proxmox_virtual_environment_file.meta_config.id
|
||||||
}
|
}
|
||||||
|
|
||||||
name = "terraform-provider-proxmox-example-template"
|
machine = "q35"
|
||||||
|
name = "terraform-provider-proxmox-example-template"
|
||||||
|
|
||||||
network_device {
|
network_device {
|
||||||
mtu = 1450
|
mtu = 1450
|
||||||
@ -113,6 +115,8 @@ resource "proxmox_virtual_environment_vm" "example" {
|
|||||||
vm_id = proxmox_virtual_environment_vm.example_template.id
|
vm_id = proxmox_virtual_environment_vm.example_template.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
machine = "q35"
|
||||||
|
|
||||||
memory {
|
memory {
|
||||||
dedicated = 768
|
dedicated = 768
|
||||||
}
|
}
|
||||||
@ -135,7 +139,7 @@ resource "proxmox_virtual_environment_vm" "example" {
|
|||||||
// if unspecified:
|
// if unspecified:
|
||||||
// - autodetected if there is a cloud-init device on the template
|
// - autodetected if there is a cloud-init device on the template
|
||||||
// - otherwise defaults to ide2
|
// - otherwise defaults to ide2
|
||||||
interface = "ide0"
|
interface = "scsi4"
|
||||||
|
|
||||||
dns {
|
dns {
|
||||||
server = "8.8.8.8"
|
server = "8.8.8.8"
|
||||||
|
@ -43,6 +43,40 @@ func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatus
|
|||||||
return resBody.Data, nil
|
return resBody.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTaskLog retrieves the log of a task. The log is returned as an array of
|
||||||
|
// lines. Each line is an object with a line number and the text of the line.
|
||||||
|
// Reads first 50 lines by default.
|
||||||
|
func (c *Client) GetTaskLog(ctx context.Context, upid string) ([]string, error) {
|
||||||
|
resBody := &GetTaskLogResponseBody{}
|
||||||
|
lines := []string{}
|
||||||
|
|
||||||
|
path, err := c.BuildPath(upid, "log")
|
||||||
|
if err != nil {
|
||||||
|
return lines, fmt.Errorf("error building path for task status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.DoRequest(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
path,
|
||||||
|
nil,
|
||||||
|
resBody,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return lines, fmt.Errorf("error retrieving task status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBody.Data == nil {
|
||||||
|
return lines, api.ErrNoDataObjectInResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range resBody.Data {
|
||||||
|
lines = append(lines, line.LineText)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WaitForTask waits for a specific task to complete.
|
// WaitForTask waits for a specific task to complete.
|
||||||
func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delaySec int) error {
|
func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delaySec int) error {
|
||||||
timeDelay := int64(delaySec)
|
timeDelay := int64(delaySec)
|
||||||
|
@ -25,6 +25,17 @@ type GetTaskStatusResponseData struct {
|
|||||||
ExitCode string `json:"exitstatus,omitempty"`
|
ExitCode string `json:"exitstatus,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTaskLogResponseBody contains the body from a node get task log response.
|
||||||
|
type GetTaskLogResponseBody struct {
|
||||||
|
Data []*GetTaskLogResponseData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskLogResponseData contains the data from a node get task log response.
|
||||||
|
type GetTaskLogResponseData struct {
|
||||||
|
LineNumber int `json:"n,omitempty"`
|
||||||
|
LineText string `json:"t,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// TaskID contains the components of a PVE task ID.
|
// TaskID contains the components of a PVE task ID.
|
||||||
type TaskID struct {
|
type TaskID struct {
|
||||||
NodeName string
|
NodeName string
|
||||||
|
@ -330,18 +330,33 @@ func (c *Client) ShutdownVMAsync(ctx context.Context, d *ShutdownRequestBody) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartVM starts a virtual machine.
|
// StartVM starts a virtual machine.
|
||||||
func (c *Client) StartVM(ctx context.Context, timeout int) error {
|
// Returns the task log if the VM had warnings at startup, or fails to start.
|
||||||
|
func (c *Client) StartVM(ctx context.Context, timeout int) ([]string, error) {
|
||||||
taskID, err := c.StartVMAsync(ctx)
|
taskID, err := c.StartVMAsync(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error waiting for VM start: %w", err)
|
log, e := c.Tasks().GetTaskLog(ctx, *taskID)
|
||||||
|
if e != nil {
|
||||||
|
tflog.Error(ctx, "error retrieving task log", map[string]interface{}{
|
||||||
|
"task_id": *taskID,
|
||||||
|
"error": e.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
|
log = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(err.Error(), "WARNING") && len(log) > 0 {
|
||||||
|
return log, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return log, fmt.Errorf("error waiting for VM start: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartVMAsync starts a virtual machine asynchronously.
|
// StartVMAsync starts a virtual machine asynchronously.
|
||||||
|
@ -256,13 +256,12 @@ func IDEInterface() schema.SchemaValidateDiagFunc {
|
|||||||
// CloudInitInterface is a schema validation function that accepts either an IDE interface identifier or an
|
// CloudInitInterface is a schema validation function that accepts either an IDE interface identifier or an
|
||||||
// empty string, which is used as the default and means "detect which interface should be used automatically".
|
// empty string, which is used as the default and means "detect which interface should be used automatically".
|
||||||
func CloudInitInterface() schema.SchemaValidateDiagFunc {
|
func CloudInitInterface() schema.SchemaValidateDiagFunc {
|
||||||
return validation.ToDiagFunc(validation.StringInSlice([]string{
|
r := regexp.MustCompile(`^ide[0-3]|sata[0-5]|scsi(?:30|[12][0-9]|[0-9])$`)
|
||||||
"",
|
|
||||||
"ide0",
|
return validation.ToDiagFunc(validation.Any(
|
||||||
"ide1",
|
validation.StringIsEmpty,
|
||||||
"ide2",
|
validation.StringMatch(r, "one of ide0..3|sata0..5|scsi0..30"),
|
||||||
"ide3",
|
))
|
||||||
}, false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloudInitType is a schema validation function for cloud-init types.
|
// CloudInitType is a schema validation function for cloud-init types.
|
||||||
|
@ -1602,16 +1602,26 @@ func deleteIdeDrives(ctx context.Context, vmAPI *vms.Client, itf1 string, itf2 s
|
|||||||
|
|
||||||
// Start the VM, then wait for it to actually start; it may not be started immediately if running in HA mode.
|
// Start the VM, then wait for it to actually start; it may not be started immediately if running in HA mode.
|
||||||
func vmStart(ctx context.Context, vmAPI *vms.Client, d *schema.ResourceData) diag.Diagnostics {
|
func vmStart(ctx context.Context, vmAPI *vms.Client, d *schema.ResourceData) diag.Diagnostics {
|
||||||
|
var diags diag.Diagnostics
|
||||||
|
|
||||||
tflog.Debug(ctx, "Starting VM")
|
tflog.Debug(ctx, "Starting VM")
|
||||||
|
|
||||||
startVMTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutStartVM).(int)
|
startVMTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutStartVM).(int)
|
||||||
|
|
||||||
e := vmAPI.StartVM(ctx, startVMTimeout)
|
log, e := vmAPI.StartVM(ctx, startVMTimeout)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return diag.FromErr(e)
|
return append(diags, diag.FromErr(e)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return diag.FromErr(vmAPI.WaitForVMState(ctx, "running", startVMTimeout, 1))
|
if len(log) > 0 {
|
||||||
|
lines := "\n\t| " + strings.Join(log, "\n\t| ")
|
||||||
|
diags = append(diags, diag.Diagnostic{
|
||||||
|
Severity: diag.Warning,
|
||||||
|
Summary: fmt.Sprintf("the VM startup task finished with a warning, task log:\n%s", lines),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(diags, diag.FromErr(vmAPI.WaitForVMState(ctx, "running", startVMTimeout, 1))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the VM, then wait for it to actually shut down (it may not be shut down immediately if
|
// Shutdown the VM, then wait for it to actually shut down (it may not be shut down immediately if
|
||||||
@ -3779,12 +3789,16 @@ func vmReadCustom(
|
|||||||
diskObjects := getDiskInfo(vmConfig, d)
|
diskObjects := getDiskInfo(vmConfig, d)
|
||||||
|
|
||||||
for di, dd := range diskObjects {
|
for di, dd := range diskObjects {
|
||||||
disk := map[string]interface{}{}
|
|
||||||
|
|
||||||
if dd == nil || dd.FileVolume == "none" || strings.HasPrefix(di, "ide") {
|
if dd == nil || dd.FileVolume == "none" || strings.HasPrefix(di, "ide") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(dd.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
disk := map[string]interface{}{}
|
||||||
|
|
||||||
fileIDParts := strings.Split(dd.FileVolume, ":")
|
fileIDParts := strings.Split(dd.FileVolume, ":")
|
||||||
|
|
||||||
disk[mkResourceVirtualEnvironmentVMDiskDatastoreID] = fileIDParts[0]
|
disk[mkResourceVirtualEnvironmentVMDiskDatastoreID] = fileIDParts[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user