Skip to content

Commit 202b466

Browse files
calibraecalidmacvicar
authored
feat: add terraform import support for libvirt_domain (#1303)
feat: add terraform import support for libvirt_domain Import existing domains by UUID or name: terraform import libvirt_domain.myvm <uuid> terraform import libvirt_domain.myvm <name> Populates full state from domain XML including devices, CPU, memory, boot config, autostart, and running status. * docs: replace real UUID with placeholder in import example * docs: revert generated domain.md to upstream state * docs: remove fork-specific README note * fix: use canonical domain UUID for import --------- Co-authored-by: cali <cali@mini.calii.lan> Co-authored-by: Duncan Mac-Vicar P. <duncan@mac-vicar.eu>
1 parent 7e37d42 commit 202b466

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ If you contribute code or issues and used AI, you are required to disclose it, i
305305

306306
## Author
307307

308-
* Duncan Mac-Vicar P.
308+
* Duncan Mac-Vicar P. (upstream)
309309

310310
## License
311311

internal/provider/domain_resource.go

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/dmacvicar/terraform-provider-libvirt/v2/internal/libvirt"
1111
"github.com/hashicorp/terraform-plugin-framework/attr"
1212
"github.com/hashicorp/terraform-plugin-framework/diag"
13+
"github.com/hashicorp/terraform-plugin-framework/path"
1314
"github.com/hashicorp/terraform-plugin-framework/resource"
1415
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1516
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -19,8 +20,9 @@ import (
1920

2021
// Ensure the implementation satisfies the expected interfaces
2122
var (
22-
_ resource.Resource = &DomainResource{}
23-
_ resource.ResourceWithConfigure = &DomainResource{}
23+
_ resource.Resource = &DomainResource{}
24+
_ resource.ResourceWithConfigure = &DomainResource{}
25+
_ resource.ResourceWithImportState = &DomainResource{}
2426
)
2527

2628
// NewDomainResource creates a new domain resource
@@ -860,10 +862,21 @@ func (r *DomainResource) Read(ctx context.Context, req resource.ReadRequest, res
860862
return
861863
}
862864

863-
planData, diags := prepareDomainPlan(ctx, &state)
864-
resp.Diagnostics.Append(diags...)
865-
if resp.Diagnostics.HasError() {
866-
return
865+
// Detect import: after ImportState, only UUID is set — Name will be null.
866+
// When importing, pass nil as plan so DomainFromXML populates all fields from XML.
867+
isImport := state.Name.IsNull() || state.Name.IsUnknown()
868+
869+
var plan *generated.DomainModel
870+
var waitAttrs []attr.Value
871+
872+
if !isImport {
873+
planData, diags := prepareDomainPlan(ctx, &state)
874+
resp.Diagnostics.Append(diags...)
875+
if resp.Diagnostics.HasError() {
876+
return
877+
}
878+
plan = &planData.SanitizedModel
879+
waitAttrs = planData.WaitAttributes
867880
}
868881

869882
domain, err := r.client.LookupDomainByUUID(state.UUID.ValueString())
@@ -890,7 +903,7 @@ func (r *DomainResource) Read(ctx context.Context, req resource.ReadRequest, res
890903
return
891904
}
892905

893-
stateModel, err := generated.DomainFromXML(ctx, parsedDomain, &planData.SanitizedModel)
906+
stateModel, err := generated.DomainFromXML(ctx, parsedDomain, plan)
894907
if err != nil {
895908
resp.Diagnostics.AddError(
896909
"Failed to Convert Domain",
@@ -901,10 +914,15 @@ func (r *DomainResource) Read(ctx context.Context, req resource.ReadRequest, res
901914

902915
state.DomainModel = *stateModel
903916

904-
state.Devices, diags = applyWaitForIPValues(ctx, state.Devices, planData.WaitAttributes)
905-
resp.Diagnostics.Append(diags...)
906-
if resp.Diagnostics.HasError() {
907-
return
917+
// Always apply wait_for_ip type conversion — the schema expects it.
918+
// During import, waitAttrs is nil so all interfaces get null wait_for_ip.
919+
{
920+
var diags diag.Diagnostics
921+
state.Devices, diags = applyWaitForIPValues(ctx, state.Devices, waitAttrs)
922+
resp.Diagnostics.Append(diags...)
923+
if resp.Diagnostics.HasError() {
924+
return
925+
}
908926
}
909927

910928
if !originalMetadata.IsNull() && !originalMetadata.IsUnknown() {
@@ -915,7 +933,8 @@ func (r *DomainResource) Read(ctx context.Context, req resource.ReadRequest, res
915933
state.ID = originalID
916934
}
917935

918-
if !state.Autostart.IsNull() && !state.Autostart.IsUnknown() {
936+
// Read autostart — always during import, conditionally otherwise
937+
if isImport || (!state.Autostart.IsNull() && !state.Autostart.IsUnknown()) {
919938
autostart, err := r.client.Libvirt().DomainGetAutostart(domain)
920939
if err != nil {
921940
resp.Diagnostics.AddError(
@@ -927,9 +946,31 @@ func (r *DomainResource) Read(ctx context.Context, req resource.ReadRequest, res
927946
state.Autostart = types.BoolValue(autostart == 1)
928947
}
929948

949+
// Read running state — always during import, preserve existing otherwise
950+
if isImport {
951+
domainState, _, err := r.client.Libvirt().DomainGetState(domain, 0)
952+
if err != nil {
953+
resp.Diagnostics.AddError(
954+
"Failed to Get Domain State",
955+
"Failed to read domain running state: "+err.Error(),
956+
)
957+
return
958+
}
959+
state.Running = types.BoolValue(uint32(domainState) == uint32(golibvirt.DomainRunning))
960+
}
961+
930962
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
931963
}
932964

965+
// ImportState imports an existing libvirt domain by UUID.
966+
//
967+
// Usage:
968+
//
969+
// terraform import libvirt_domain.myvm <uuid>
970+
func (r *DomainResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
971+
resource.ImportStatePassthroughID(ctx, path.Root("uuid"), req, resp)
972+
}
973+
933974
// waitForDomainState waits for a domain to reach the specified state with a timeout
934975
func waitForDomainState(client *libvirt.Client, domain golibvirt.Domain, targetState uint32, timeout time.Duration) error {
935976
deadline := time.Now().Add(timeout)

0 commit comments

Comments
 (0)