vm

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 18, 2026 License: MPL-2.0 Imports: 26 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

View Source
var BootOrderEntryAttrTypes = map[string]attr.Type{
	"type":                types.StringType,
	"controller_type":     types.StringType,
	"controller_number":   types.Int64Type,
	"controller_location": types.Int64Type,
	"name":                types.StringType,
}

BootOrderEntryAttrTypes mirrors BootOrderEntryModel's tfsdk-tagged fields. Same role as DvdDriveAttrTypes for the boot_order list.

View Source
var BootOrderEntryListElementType = types.ObjectType{AttrTypes: BootOrderEntryAttrTypes}

BootOrderEntryListElementType is the Object element type the boot_order list holds.

View Source
var DvdDriveAttrTypes = map[string]attr.Type{
	"iso_path":            pathtype.Type,
	"controller_type":     types.StringType,
	"controller_number":   types.Int64Type,
	"controller_location": types.Int64Type,
}

DvdDriveAttrTypes mirrors DvdDriveModel's tfsdk-tagged fields. Used by the Object element type that backs the dvd_drive list, by the helpers below for ListValueFrom / ElementsAs round-trips, and by schema.go's Default empty-list value.

View Source
var DvdDriveListElementType = types.ObjectType{AttrTypes: DvdDriveAttrTypes}

DvdDriveListElementType is the Object element type the dvd_drive list holds. Computed once so all helpers share the same instance.

Functions

func BootOrderListFromSlice added in v0.3.0

func BootOrderListFromSlice(ctx context.Context, slice []BootOrderEntryModel) (types.List, diag.Diagnostics)

BootOrderListFromSlice builds a types.List from a typed slice. Nil slice -> null list; empty slice -> empty list. Same rationale as DvdDriveListFromSlice.

func DvdDriveListFromSlice added in v0.3.0

func DvdDriveListFromSlice(ctx context.Context, slice []DvdDriveModel) (types.List, diag.Diagnostics)

DvdDriveListFromSlice builds a types.List from a typed slice. A nil slice becomes a null list (matching the "DvdDrives not managed" semantics on a fresh VM); an empty slice becomes an empty list (the schema's Default value when the user omits the attribute).

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 and BootOrder are types.List rather than []Struct so the
	// framework can represent unknown values cleanly. The framework's
	// reflect path can't fit "the whole list is unknown" into a Go slice
	// (no representation for it), and emits "Value Conversion Error /
	// Suggested Type: basetypes.ListValue" when a config drives the
	// attribute from a for_each variable that hasn't materialized at
	// validate time. Same fix shape PR #70 applied to URLConfig on
	// hyperv_image_file. Helpers below give resource code typed access
	// to the underlying []DvdDriveModel / []BootOrderEntryModel slice
	// when the value is known.
	DvdDrives          types.List   `tfsdk:"dvd_drive"`
	BootOrder          types.List   `tfsdk:"boot_order"`
	SecureBoot         types.Bool   `tfsdk:"secure_boot"`
	SecureBootTemplate types.String `tfsdk:"secure_boot_template"`
	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.

func (*Model) BootOrderEntries added in v0.3.0

func (m *Model) BootOrderEntries(ctx context.Context) ([]BootOrderEntryModel, diag.Diagnostics)

BootOrderEntries returns the typed slice underlying m.BootOrder, or nil if null/unknown. Same null-vs-empty rationale as DvdDriveModels.

func (*Model) DvdDriveModels added in v0.3.0

func (m *Model) DvdDriveModels(ctx context.Context) ([]DvdDriveModel, diag.Diagnostics)

DvdDriveModels returns the typed slice underlying m.DvdDrives, or nil if the list is null or unknown. Resource code that needs to distinguish "user explicitly set []" from "user did not set the attribute" should inspect m.DvdDrives directly via IsNull / IsUnknown -- a known-empty list returns an empty (but non-nil) slice here.

type NetworkAdapterModel

type NetworkAdapterModel struct {
	Name        types.String `tfsdk:"name"`
	SwitchName  types.String `tfsdk:"switch_name"`
	IPAddresses types.List   `tfsdk:"ip_addresses"`
	MacAddress  mactype.MAC  `tfsdk:"mac_address"`
	VlanID      types.Int64  `tfsdk:"vlan_id"`
}

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.

IPAddresses is the per-NIC slice of IPv4 / IPv6 addresses that Hyper-V's integration services have reported for this specific adapter. Computed -- populated on Read from the host. Unlike the VM-level flat `ip_addresses` list, the per-NIC view gives multi- homed VMs a stable reference to a specific NIC's IPs (order within a single NIC is host-driven but the NIC selector itself is keyed by the deterministic display Name).

MacAddress is Optional (not Computed). When the user sets it, the NIC uses a static MAC of that value. When unset, Hyper-V auto- assigns from its dynamic-MAC pool; Read writes null to state in that case (we don't store the dynamically-assigned MAC -- that would create a perpetual plan diff against the empty config). Optional-only is deliberate: with Computed, the framework copies state-value forward when config goes null, which masks intentional reverts to dynamic-MAC mode (omitted-vs-explicit-null are indistinguishable at the framework's config layer).

VlanID is Optional (not Computed) for the same reason. When set to 1-4094, the NIC is tagged with that VLAN ID in Access mode. When unset, the NIC carries untagged frames. Read populates from Get-VMNetworkAdapterVlan.AccessVlanId; an untagged NIC produces state value null (matching unset config to avoid perpetual diff).

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 (v4) shape; the framework dispatches based on the on-disk version, NOT a chain.

Each upgradeNToM helper returns the current Model, so a v0 state file goes v0 -> Model in one hop (the helper handles every shape change between v0 and v4 inline). When a new schema version lands, add a new priorSchemaVX / priorModelVX / upgradeVXToV(X+1) triple here and update the existing helpers to populate any new fields with their defaulted-or-null v4 shape.

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