vm

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2026 License: MPL-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package vm implements the hyperv_vm resource (M4 minimal first slice). Wraps the vm/{get,new,set,remove}.ps1 contract via the typed hyperv.Client.

Excluded from this slice (each becomes its own follow-up PR):

  • boot_order (gen1 BIOS / gen2 UEFI translation deserves its own design)
  • dynamic memory (min_bytes / max_bytes / buffer / priority on the existing memory block)
  • integration services map
  • automatic start/stop actions
  • checkpoint type/policy
  • VM path overrides (defaults from Get-VMHost)

CPU and memory live in nested blocks per ADR-0001: `cpu = { count = N }` and `memory = { startup_bytes = N }`. The blocks exist as nested attributes (rather than flat top-level fields) so dynamic-CPU and dynamic-memory follow-ups attach to the same block without flattening more attribute names into the top namespace -- e.g. cpu.weight, cpu.reserve, memory.min_bytes, memory.max_bytes.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New() resource.Resource

New is the framework factory.

Types

type BootOrderEntryModel

type BootOrderEntryModel struct {
	Type               types.String `tfsdk:"type"`
	ControllerType     types.String `tfsdk:"controller_type"`
	ControllerNumber   types.Int64  `tfsdk:"controller_number"`
	ControllerLocation types.Int64  `tfsdk:"controller_location"`
	Name               types.String `tfsdk:"name"`
}

BootOrderEntryModel is one element of the `boot_order` list on a gen 2 hyperv_vm. Type discriminates between hard_disk_drive / dvd_drive entries (which carry the slot tuple) and network_adapter entries (which carry Name). Unused fields for a given Type are null.

Gen 1 BIOS startup order is a separate, deferred slice; the schema validator rejects boot_order on gen 1 at plan time.

type CPUModel

type CPUModel struct {
	Count types.Int64 `tfsdk:"count"`
}

CPUModel is the nested `cpu` block. Static count only in this slice; dynamic-CPU attributes (weight, reserve, limit) attach as additional fields here in a follow-up.

type DvdDriveModel

type DvdDriveModel struct {
	IsoPath            pathtype.Path `tfsdk:"iso_path"`
	ControllerType     types.String  `tfsdk:"controller_type"`
	ControllerNumber   types.Int64   `tfsdk:"controller_number"`
	ControllerLocation types.Int64   `tfsdk:"controller_location"`
}

DvdDriveModel is one element of the `dvd_drive` list on hyperv_vm. Same slot-tuple shape as HardDiskDriveModel (controller_type, controller_number, controller_location), but IsoPath is Optional -- an empty DVD drive (no medium loaded) is a legitimate config.

IsoPath uses pathtype.Path for slash-style folding consistent with hyperv_image_file.destination_path and hyperv_vhd.path.

type HardDiskDriveModel

type HardDiskDriveModel struct {
	Path               pathtype.Path `tfsdk:"path"`
	ControllerType     types.String  `tfsdk:"controller_type"`
	ControllerNumber   types.Int64   `tfsdk:"controller_number"`
	ControllerLocation types.Int64   `tfsdk:"controller_location"`
}

HardDiskDriveModel is one element of the `hard_disk_drive` nested set on hyperv_vm. Identifies an attached VHD (Path) at a specific controller slot (ControllerType + ControllerNumber + ControllerLocation).

Path uses pathtype.Path for slash/case folding consistent with hyperv_vhd.path and hyperv_image_file.destination_path -- a VHD path the user wrote with forward slashes round-trips through the bench's canonical backslash form without phantom diffs.

type MemoryModel

type MemoryModel struct {
	StartupBytes types.Int64 `tfsdk:"startup_bytes"`
	Dynamic      types.Bool  `tfsdk:"dynamic"`
	MinBytes     types.Int64 `tfsdk:"min_bytes"`
	MaxBytes     types.Int64 `tfsdk:"max_bytes"`
}

MemoryModel is the nested `memory` block. StartupBytes is the only required field; Dynamic / MinBytes / MaxBytes opt in to Hyper-V's dynamic memory mode.

Dynamic is types.Bool (not pointer) because the framework's null representation handles both "user didn't manage" and "explicit false" cleanly via the wire-side *bool with omitempty: null on the wire means absent, which the script treats as static. MinBytes and MaxBytes follow the same Optional+Computed + UseStateForUnknown pattern as state.shutdown_mode (PR #33).

Buffer and Priority are deferred -- they're Hyper-V dynamic-memory niceties (~5% of users) and adding them later is a strict superset of the current schema.

type Model

type Model struct {
	ID              types.String          `tfsdk:"id"`
	Name            types.String          `tfsdk:"name"`
	Generation      types.Int64           `tfsdk:"generation"`
	CPU             *CPUModel             `tfsdk:"cpu"`
	Memory          *MemoryModel          `tfsdk:"memory"`
	HardDiskDrives  []HardDiskDriveModel  `tfsdk:"hard_disk_drive"`
	NetworkAdapters []NetworkAdapterModel `tfsdk:"network_adapter"`
	DvdDrives       []DvdDriveModel       `tfsdk:"dvd_drive"`
	BootOrder       []BootOrderEntryModel `tfsdk:"boot_order"`
	SecureBoot      types.Bool            `tfsdk:"secure_boot"`
	Notes           types.String          `tfsdk:"notes"`
	State           *StateModel           `tfsdk:"state"`
	IPAddresses     types.List            `tfsdk:"ip_addresses"`
	Path            types.String          `tfsdk:"path"`
}

Model is the tfsdk-bound struct backing the resource state. Field tags align with schema.go attribute names; conversion to/from the typed hyperv.VM DTO lives in resource.go.

SecureBoot is types.Bool (not pointer-to-bool) -- the framework's null representation handles the gen-1 case where the host has no Secure Boot concept and the cmdlet returns null.

CPU and Memory are pointer-typed (not value) because at ImportState time the state passes through with just `name` set; the framework then calls Read to populate everything else, but in the brief window before Read runs, cpu/memory are null in state. A value type can't represent null and the framework errors with "Received null value, however the target type cannot handle null values." Pointers null cleanly; modelFromVM allocates them.

HardDiskDrives is a list of attachments stored canonically by slot tuple (controller_type, controller_number, controller_location). Schema-side ListNestedAttribute (rather than SetNestedAttribute) because terraform-plugin-framework v1.19's slice decode of nested-set attributes hits a reflect path that produces a "Target Type: []vm.HardDiskDriveModel, Suggested Type: basetypes.SetValue" error during req.Plan.Get. List + a canonical sort in modelFromVM gives the same user-visible behavior (HCL ordering matches canonical state on subsequent applies) with a simpler decode.

type NetworkAdapterModel

type NetworkAdapterModel struct {
	Name       types.String `tfsdk:"name"`
	SwitchName types.String `tfsdk:"switch_name"`
}

NetworkAdapterModel is one element of the `network_adapter` list on hyperv_vm. Display Name is the slot key for diff/reconciliation (Hyper-V allows duplicate-named NICs at the cmdlet level, but the resource-layer schema validator enforces uniqueness within a VM's list at plan time, so the slot key is well-defined).

SwitchName binds the NIC to a hyperv_virtual_switch by name. Required in this slice; future commits may add Optional defaulting to "unbound" if a real use case surfaces.

type Resource

type Resource struct {
	// contains filtered or unexported fields
}

Resource implements hyperv_vm.

func (*Resource) ConfigValidators

func (r *Resource) ConfigValidators(_ context.Context) []resource.ConfigValidator

ConfigValidators rejects mode/attribute combinations at plan time so the operator gets a clear, attribute-anchored diagnostic instead of the cmdlet's opaque error at apply time.

func (*Resource) Configure

Configure stashes the typed Hyper-V client built by the provider's Configure pass. Skips when ProviderData is nil (validate-time invocation before the provider has resolved its config).

func (*Resource) Create

Create runs new.ps1 with the plan's attributes and writes the post-create read shape back to state.

func (*Resource) Delete

Delete runs remove.ps1. ErrNotFound is treated as success (the VM is already gone). The script hard-stops the VM first if it's running -- this is the one place the PS layer drives a power transition. Hard stop (Stop-VM -Force -TurnOff) instead of graceful for the reasons documented in the resource MarkdownDescription: graceful shutdown hangs indefinitely on guests with absent / unresponsive integration services, and destroy semantics across IaC providers consistently match the "destroy means destroy" expectation.

func (*Resource) ImportState

ImportState lets `terraform import hyperv_vm.foo my-vm` work by treating the import ID as the VM name. Read populates the rest of the attributes via Get-VM on the immediately-following refresh.

func (*Resource) Metadata

Metadata sets the resource's TF type name.

func (*Resource) Read

Read fetches the current shape via get.ps1 and reconciles state.

ErrNotFound -> RemoveResource so Terraform plans recreate. Other errors -> AddError so a transient fault doesn't silently drop the resource from state.

func (*Resource) Schema

Schema returns the locked-in schema (see schema.go).

func (*Resource) Update

Update forwards only the fields that changed between state and plan to avoid hitting Set-VMMemory / Set-VMProcessor needlessly on a running VM (those cmdlets validate state by parameter set, not value semantics -- even a no-op call to Set-VMMemory on a running VM errors). Generation is always forwarded as the script's gen-2-only SecureBoot guard hint.

func (*Resource) UpgradeState

func (r *Resource) UpgradeState(_ context.Context) map[int64]resource.StateUpgrader

UpgradeState bridges schema versions for hyperv_vm. Each entry maps from a SOURCE version directly to the current (v2) shape; the framework dispatches based on the on-disk version, NOT a chain.

type StateModel

type StateModel struct {
	Desired      types.String `tfsdk:"desired"`
	Current      types.String `tfsdk:"current"`
	ShutdownMode types.String `tfsdk:"shutdown_mode"`
}

StateModel is the nested `state` block on hyperv_vm. Pointer-typed (Model.State is *StateModel) for the same reason as CPU and Memory: during ImportState the framework writes a partial Model with just `name` set, and a value-typed nested struct can't represent null.

Desired is the user-facing power-state input ("Off" | "Running"); a transition fires only when Desired differs from the host's actual state. Current is the Computed readback from Hyper-V; useful for downstream resources that key off the actual state ("provision the guest only when state.current = Running").

Saved and Paused are out of scope for this slice -- the schema validator on Desired rejects values outside {Off, Running}. Drift from those states surfaces verbatim in Current; the next Update will hard-power-off or start as configured.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL