Skip to content

Commit

Permalink
fet(vm): allow scsi and sata interfaces for CloudInit Drive (#598)
Browse files Browse the repository at this point in the history
* fet(vm): allow `scsi` and `sata` interfaces for CloudInit Drive

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
  • Loading branch information
bpg authored Oct 2, 2023
1 parent fa590ed commit 0b8f2e2
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 21 deletions.
5 changes: 3 additions & 2 deletions docs/resources/virtual_environment_vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ output "ubuntu_vm_public_key" {
- `datastore_id` - (Optional) The identifier for the datastore to create the
cloud-init disk in (defaults to `local-lvm`).
- `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
cloud-init image is present, otherwise defaults to `ide2`.
image to. Must be one of `ide0..3`, `sata0..5`, `scsi0..30`. Will be
detected if the setting is missing but a cloud-init image is present,
otherwise defaults to `ide2`.
- `dns` - (Optional) The DNS configuration.
- `domain` - (Optional) The DNS search domain.
- `server` - (Optional) The DNS server.
Expand Down
10 changes: 7 additions & 3 deletions example/resource_virtual_environment_vm.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ resource "proxmox_virtual_environment_vm" "example_template" {
enabled = true
}

bios = "ovmf"
description = "Managed by Terraform"

cpu {
Expand Down Expand Up @@ -56,7 +57,7 @@ resource "proxmox_virtual_environment_vm" "example_template" {

initialization {
datastore_id = local.datastore_id
# interface = "ide2"
interface = "scsi4"

dns {
server = "1.1.1.1"
Expand All @@ -76,7 +77,8 @@ resource "proxmox_virtual_environment_vm" "example_template" {
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 {
mtu = 1450
Expand Down Expand Up @@ -113,6 +115,8 @@ resource "proxmox_virtual_environment_vm" "example" {
vm_id = proxmox_virtual_environment_vm.example_template.id
}

machine = "q35"

memory {
dedicated = 768
}
Expand All @@ -135,7 +139,7 @@ resource "proxmox_virtual_environment_vm" "example" {
// if unspecified:
// - autodetected if there is a cloud-init device on the template
// - otherwise defaults to ide2
interface = "ide0"
interface = "scsi4"

dns {
server = "8.8.8.8"
Expand Down
34 changes: 34 additions & 0 deletions proxmox/nodes/tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,40 @@ func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatus
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.
func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delaySec int) error {
timeDelay := int64(delaySec)
Expand Down
11 changes: 11 additions & 0 deletions proxmox/nodes/tasks/tasks_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ type GetTaskStatusResponseData struct {
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.
type TaskID struct {
NodeName string
Expand Down
23 changes: 19 additions & 4 deletions proxmox/nodes/vms/vms.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,18 +330,33 @@ func (c *Client) ShutdownVMAsync(ctx context.Context, d *ShutdownRequestBody) (*
}

// 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)
if err != nil {
return err
return nil, err
}

err = c.Tasks().WaitForTask(ctx, *taskID, timeout, 5)
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.
Expand Down
13 changes: 6 additions & 7 deletions proxmoxtf/resource/validator/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,12 @@ func IDEInterface() schema.SchemaValidateDiagFunc {
// 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".
func CloudInitInterface() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"",
"ide0",
"ide1",
"ide2",
"ide3",
}, false))
r := regexp.MustCompile(`^ide[0-3]|sata[0-5]|scsi(?:30|[12][0-9]|[0-9])$`)

return validation.ToDiagFunc(validation.Any(
validation.StringIsEmpty,
validation.StringMatch(r, "one of ide0..3|sata0..5|scsi0..30"),
))
}

// CloudInitType is a schema validation function for cloud-init types.
Expand Down
24 changes: 19 additions & 5 deletions proxmoxtf/resource/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
func vmStart(ctx context.Context, vmAPI *vms.Client, d *schema.ResourceData) diag.Diagnostics {
var diags diag.Diagnostics

tflog.Debug(ctx, "Starting VM")

startVMTimeout := d.Get(mkResourceVirtualEnvironmentVMTimeoutStartVM).(int)

e := vmAPI.StartVM(ctx, startVMTimeout)
log, e := vmAPI.StartVM(ctx, startVMTimeout)
if e != nil {
return diag.FromErr(e)
return append(diags, diag.FromErr(e)...)
}

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 diag.FromErr(vmAPI.WaitForVMState(ctx, "running", startVMTimeout, 1))
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
Expand Down Expand Up @@ -3779,12 +3789,16 @@ func vmReadCustom(
diskObjects := getDiskInfo(vmConfig, d)

for di, dd := range diskObjects {
disk := map[string]interface{}{}

if dd == nil || dd.FileVolume == "none" || strings.HasPrefix(di, "ide") {
continue
}

if strings.HasSuffix(dd.FileVolume, fmt.Sprintf("vm-%d-cloudinit", vmID)) {
continue
}

disk := map[string]interface{}{}

fileIDParts := strings.Split(dd.FileVolume, ":")

disk[mkResourceVirtualEnvironmentVMDiskDatastoreID] = fileIDParts[0]
Expand Down

0 comments on commit 0b8f2e2

Please sign in to comment.