Documentation
¶
Overview ¶
Package image_file implements the hyperv_image_file resource. Wraps the image_file/{get,new,remove}.ps1 contract via the typed hyperv.Client.
Index ¶
- Variables
- func New() resource.Resource
- func URLObjectFromConfig(ctx context.Context, u *URLConfig) (types.Object, diag.Diagnostics)
- type Model
- type Resource
- func (r *Resource) ConfigValidators(_ context.Context) []resource.ConfigValidator
- func (r *Resource) Configure(_ context.Context, req resource.ConfigureRequest, ...)
- func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse)
- func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse)
- func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, ...)
- func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, ...)
- func (r *Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, ...)
- func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse)
- func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse)
- func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse)
- type URLConfig
Constants ¶
This section is empty.
Variables ¶
var URLAttrTypes = map[string]attr.Type{ "url": types.StringType, "checksum": types.StringType, "compression": types.StringType, }
URLAttrTypes mirrors the SingleNestedAttribute "url" shape in schema.go. Used by types.Object construction (ObjectValueFrom) and decode (Object.As).
Functions ¶
func URLObjectFromConfig ¶ added in v0.3.0
URLObjectFromConfig builds a types.Object from a *URLConfig. A nil pointer becomes a null Object (matching the "URL not set" semantics of the previous *URLConfig field). Used by modelFromImageFile and any test code that constructs a Model with a known URL block.
Types ¶
type Model ¶
type Model struct {
ID pathtype.Path `tfsdk:"id"`
DestinationPath pathtype.Path `tfsdk:"destination_path"`
URL types.Object `tfsdk:"url"`
LocalPath pathtype.Path `tfsdk:"local_path"`
ContentBase64 types.String `tfsdk:"content_base64"`
ReplaceWhileMounted types.Bool `tfsdk:"replace_while_mounted"`
Sha256 types.String `tfsdk:"sha256"`
SizeBytes types.Int64 `tfsdk:"size_bytes"`
KeepOnDestroy types.Bool `tfsdk:"keep_on_destroy"`
ForceDestroy types.Bool `tfsdk:"force_destroy"`
}
Model is the tfsdk-bound struct backing the resource state. Field tags align with schema.go attribute names; conversion to/from the typed hyperv.ImageFile DTO lives in resource.go.
Four source modes, discriminated by which of URL / LocalPath / ContentBase64 is set:
- URL non-nil => url mode (HttpClient fetch)
- URL nil, LocalPath non-null => local_path mode (runner streams bytes from a runner-side file via Connection.StreamFile)
- URL nil, LocalPath null, ContentBase64 non-null => literal_bytes mode (runner decodes base64, writes to a tmpfile, streams via the same wire path local_path mode uses)
- all three nil/null => host_path mode (verify only)
All three placement-mode discriminators (URL, LocalPath, ContentBase64) are mutually exclusive; the ConfigValidator on the resource rejects configs that set more than one. All three carry RequiresReplace at the schema layer, so any mode switch destroys and recreates. The Delete path keys on the same discriminators to gate the host-side remove (host_path mode never removes, since the user attested the file already existed).
ReplaceWhileMounted is the opt-in escape hatch for re-streaming over a destination that's currently mounted as a DVD on a running VM. Only honored in local_path and literal_bytes modes -- the modes with a re-stream Update path; url-mode forces replacement on any change, and host_path-mode never writes the destination.
ForceDestroy is the opt-in escape hatch for destroying a file that is currently mounted as a DVD on a running VM. When true, Delete asks remove.ps1 to detach the holding slot(s) via Set-VMDvdDrive -Path $null before retrying the delete. Honored in url, local_path, and literal_bytes modes -- those are the modes that actually run the host-side delete. Host_path-mode skips the delete entirely so the flag is a no-op there.
DestinationPath uses the pathtype.Path custom type so users can write either `C:/foo` or `C:\foo` without the framework rejecting the apply with "Provider produced inconsistent result after apply" when Hyper-V returns the canonical backslash form. ID mirrors destination_path and is also Path so the same semantic-equality covers the Computed mirror's refresh path. LocalPath uses Path for the same slash-folding reason -- users on macOS / Linux runners typically write forward slashes for the local path even when the destination is a Windows-form path.
type Resource ¶
type Resource struct {
// contains filtered or unexported fields
}
Resource implements hyperv_image_file.
func (*Resource) ConfigValidators ¶ added in v0.2.0
func (r *Resource) ConfigValidators(_ context.Context) []resource.ConfigValidator
ConfigValidators rejects mode-attribute combinations that the wire contract can't honor, surfacing a clear attribute-anchored diagnostic at plan time instead of an opaque cmdlet error at apply time.
func (*Resource) Configure ¶
func (r *Resource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse)
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 ¶
func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse)
Create dispatches on source mode (url, local_path, or host_path) and writes the post-create read shape back to state.
url-mode: the provider fetches via HttpClient and verifies the checksum. ErrChecksumMismatch is surfaced on path.Root("url").AtName("checksum") so the diagnostic anchors to the offending attribute, not the resource.
local_path-mode: the provider streams the runner-side file through the active connection backend, then asks new.ps1 to verify the streamed bytes' SHA against the runner-computed value and atomic-rename. A host-side hash mismatch surfaces ErrChecksumMismatch on local_path (transport corruption rather than user-supplied checksum drift).
host_path-mode: the provider verifies the file already exists at destination_path. ErrNotFound is anchored to destination_path.
func (*Resource) Delete ¶
func (r *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse)
Delete runs remove.ps1 for url-mode and local_path-mode resources -- both modes mean the provider put the file on the host, so removing it on destroy is the symmetric operation. host_path-mode (URL nil AND LocalPath null) leaves the file alone: the user attested it already existed, so removing on destroy would surprise them.
`force_destroy=true` forwards into remove.ps1's detach-then-retry branch: when the initial Remove-Item hits a sharing violation whose holders are Hyper-V DVDs, the script detaches each slot and retries. Default (false) preserves the locked-file diagnostic so cross-state drift surfaces explicitly rather than silently mutating VM state.
ErrNotFound from RemoveImageFile is treated as success (the file is already gone, no need to error).
func (*Resource) ImportState ¶
func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse)
ImportState lets `terraform import hyperv_image_file.foo C:\path\file.vhdx` work by treating the import ID as the destination path. The imported resource lands in host_path mode (no url block) -- importing inherently means "this file already exists on the host." Users can convert to url-mode later by adding the block, which will trigger replacement.
func (*Resource) Metadata ¶
func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse)
Metadata sets the resource's TF type name.
func (*Resource) ModifyPlan ¶ added in v0.2.0
func (r *Resource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse)
ModifyPlan computes the runner-side SHA-256 and size of the bytes that will land on the host (read from `local_path` for local_path- mode, decoded from `content_base64` for literal_bytes-mode) at plan time and writes them into the planned `sha256` / `size_bytes` attributes. This is what makes content changes (same destination, different bytes) surface as a plan diff -- without it, `UseStateForUnknown` would carry the prior values forward and the framework would either skip the Update entirely or reject the apply with a "Provider produced inconsistent result" check on the Computed attribute that didn't match its planned value.
Both attributes must be updated together: a content change generally changes both, and the framework's post-apply consistency check triggers on either one drifting from plan to apply.
Skipped for url-mode and host_path-mode (none of the runner-side inputs are set), during destroy (no plan), and when the relevant runner-side input is itself unknown at plan time (driven from a not-yet-applied dependency).
func (*Resource) Read ¶
func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse)
Read fetches the current shape via get.ps1 and reconciles state.
ErrNotFound -> RemoveResource so Terraform plans recreate. ErrUnauthorized / ErrPSExecution -> AddError so a transient fault doesn't silently drop the resource from state.
func (*Resource) Schema ¶
func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse)
Schema returns the locked-in schema (see schema.go).
func (*Resource) Update ¶
func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse)
Update is reached for two reasons: local_path-mode bytes changed (ModifyPlan-recomputed SHA differs from state) or a non-RequiresReplace flag toggled (replace_while_mounted / keep_on_destroy). Only local_path bytes-changed actually re-streams; the flag-only path takes the SHA-equality shortcut and skips the wire entirely so a cidata-seed flag flip doesn't re-stream the full ISO over WinRM.
literal_bytes-mode bytes-changed never enters Update -- content_base64 is RequiresReplace, so a byte change triggers Destroy+Create. A literal_bytes flag toggle reaches Update with plan.Sha256 == state.Sha256 and exits via the shortcut. Same shape for url-mode and host_path-mode: every user-settable source field is RequiresReplace, so only the flag-only path is reachable.
type URLConfig ¶
type URLConfig struct {
URL types.String `tfsdk:"url"`
Checksum types.String `tfsdk:"checksum"`
Compression types.String `tfsdk:"compression"`
}
URLConfig is the user-supplied URL-mode source configuration. `url` is required when the block is present; `checksum` is optional (when omitted, the download is trusted TLS-only). `compression` is optional -- absence means "no decompression, host fetches directly"; presence flips the typed client to a runner-pipelined fetch (download + decompress on the runner, then stream decompressed bytes to the host).
The Model carries `url` as types.Object rather than *URLConfig because the framework's pointer-to-struct shape can represent null (nil) but not unknown -- and unknown is exactly what the framework marshals when the attribute is driven from a parent variable that hasn't resolved yet (e.g. each.value.url before for_each materializes). types.Object handles all three states (known/null/unknown), and the helpers below give resource code typed access when the value is known.