0
0
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:
Pavel Boldyrev 2023-10-01 22:23:11 -04:00 committed by GitHub
parent fa590ed9e9
commit 0b8f2e2c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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