Overhaul VirtFusion provider: 20 resources, 30 data sources, multipage pagination

Complete rewrite of the VirtFusion Terraform provider with full API coverage:

- 20 managed resources (server, build, SSH key, user, firewall, IP blocks, etc.)
- 30 data sources (hypervisors, packages, servers, IP blocks, self-service, etc.)
- New HTTP client with proper error handling, query parameter support, and
  automatic multipage pagination via GetAllPages (fetches all pages from
  Laravel-style paginated endpoints and merges into a single response)
- Fixed type mismatches against live API: ServerData.Suspended (int→bool),
  IPBlockData.Type (string→int), PackageData json tags (primaryStorage, etc.),
  ServerData nested CPU/Settings/Resources structure, HypervisorGroupResources
  array response
- Configurable results-per-page (default 300) on all list data sources
- Migrated CI from GitHub Actions to Gitea Actions
- Updated goreleaser config, go.mod dependencies, and examples

Verified against live VirtFusion instance at cp.vps.ezscale.tech:
all data sources return correct data with full pagination support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 02:01:16 -04:00
parent a3a16f46fa
commit 6b7430b67b
92 changed files with 18443 additions and 1488 deletions

View File

@@ -0,0 +1,637 @@
// Copyright (c) EZSCALE.
// SPDX-License-Identifier: MPL-2.0
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"terraform-provider-virtfusion/internal/client"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Ensure provider defined types fully satisfy framework interfaces.
var (
_ resource.Resource = &ServerResource{}
_ resource.ResourceWithConfigure = &ServerResource{}
_ resource.ResourceWithImportState = &ServerResource{}
)
// NewServerResource returns a new server resource.
func NewServerResource() resource.Resource {
return &ServerResource{}
}
// ServerResource defines the resource implementation.
type ServerResource struct {
client *client.Client
}
// ServerResourceModel describes the resource data model.
type ServerResourceModel struct {
// Computed
ID types.Int64 `tfsdk:"id"`
UUID types.String `tfsdk:"uuid"`
Hostname types.String `tfsdk:"hostname"`
// Required (create)
PackageID types.Int64 `tfsdk:"package_id"`
UserID types.Int64 `tfsdk:"user_id"`
HypervisorID types.Int64 `tfsdk:"hypervisor_id"`
// Optional (create)
Ipv4 types.Int64 `tfsdk:"ipv4"`
Storage types.Int64 `tfsdk:"storage"`
Memory types.Int64 `tfsdk:"memory"`
Cores types.Int64 `tfsdk:"cores"`
Traffic types.Int64 `tfsdk:"traffic"`
InboundNetworkSpeed types.Int64 `tfsdk:"inbound_network_speed"`
OutboundNetworkSpeed types.Int64 `tfsdk:"outbound_network_speed"`
StorageProfile types.Int64 `tfsdk:"storage_profile"`
NetworkProfile types.Int64 `tfsdk:"network_profile"`
DryRun types.Bool `tfsdk:"dry_run"`
AdditionalStorage1 types.Int64 `tfsdk:"additional_storage_1"`
AdditionalStorage1Profile types.Int64 `tfsdk:"additional_storage_1_profile"`
AdditionalStorage2 types.Int64 `tfsdk:"additional_storage_2"`
AdditionalStorage2Profile types.Int64 `tfsdk:"additional_storage_2_profile"`
// Optional (update-only)
Name types.String `tfsdk:"name"`
CPUThrottle types.Int64 `tfsdk:"cpu_throttle"`
VNCEnabled types.Bool `tfsdk:"vnc_enabled"`
Suspended types.Bool `tfsdk:"suspended"`
BackupPlanID types.Int64 `tfsdk:"backup_plan_id"`
CustomXML types.String `tfsdk:"custom_xml"`
OwnerUserID types.Int64 `tfsdk:"owner_user_id"`
}
func (r *ServerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server"
}
func (r *ServerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Manages a VirtFusion server.",
Attributes: map[string]schema.Attribute{
// Computed
"id": schema.Int64Attribute{
MarkdownDescription: "The server ID.",
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"uuid": schema.StringAttribute{
MarkdownDescription: "The server UUID.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"hostname": schema.StringAttribute{
MarkdownDescription: "The server hostname.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
// Required
"package_id": schema.Int64Attribute{
MarkdownDescription: "The package ID for the server.",
Required: true,
},
"user_id": schema.Int64Attribute{
MarkdownDescription: "The user ID who owns the server.",
Required: true,
},
"hypervisor_id": schema.Int64Attribute{
MarkdownDescription: "The hypervisor ID where the server will be created.",
Required: true,
},
// Optional (create)
"ipv4": schema.Int64Attribute{
MarkdownDescription: "Number of IPv4 addresses to assign. Defaults to 1.",
Optional: true,
Computed: true,
Default: int64default.StaticInt64(1),
},
"storage": schema.Int64Attribute{
MarkdownDescription: "Storage size override in GB.",
Optional: true,
},
"memory": schema.Int64Attribute{
MarkdownDescription: "Memory size override in MB.",
Optional: true,
},
"cores": schema.Int64Attribute{
MarkdownDescription: "Number of CPU cores override.",
Optional: true,
},
"traffic": schema.Int64Attribute{
MarkdownDescription: "Traffic limit override in GB.",
Optional: true,
},
"inbound_network_speed": schema.Int64Attribute{
MarkdownDescription: "Inbound network speed override in Mbps.",
Optional: true,
},
"outbound_network_speed": schema.Int64Attribute{
MarkdownDescription: "Outbound network speed override in Mbps.",
Optional: true,
},
"storage_profile": schema.Int64Attribute{
MarkdownDescription: "Storage profile ID.",
Optional: true,
},
"network_profile": schema.Int64Attribute{
MarkdownDescription: "Network profile ID.",
Optional: true,
},
"dry_run": schema.BoolAttribute{
MarkdownDescription: "If true, validates the request without creating the server.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"additional_storage_1": schema.Int64Attribute{
MarkdownDescription: "Additional storage 1 size in GB.",
Optional: true,
},
"additional_storage_1_profile": schema.Int64Attribute{
MarkdownDescription: "Additional storage 1 profile ID.",
Optional: true,
},
"additional_storage_2": schema.Int64Attribute{
MarkdownDescription: "Additional storage 2 size in GB.",
Optional: true,
},
"additional_storage_2_profile": schema.Int64Attribute{
MarkdownDescription: "Additional storage 2 profile ID.",
Optional: true,
},
// Optional (update-only)
"name": schema.StringAttribute{
MarkdownDescription: "The server display name.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"cpu_throttle": schema.Int64Attribute{
MarkdownDescription: "CPU throttle percentage (0-100).",
Optional: true,
},
"vnc_enabled": schema.BoolAttribute{
MarkdownDescription: "Whether VNC is enabled on the server.",
Optional: true,
},
"suspended": schema.BoolAttribute{
MarkdownDescription: "Whether the server is suspended.",
Optional: true,
},
"backup_plan_id": schema.Int64Attribute{
MarkdownDescription: "Backup plan ID. Set to 0 to remove the backup plan.",
Optional: true,
},
"custom_xml": schema.StringAttribute{
MarkdownDescription: "Custom XML configuration for the server.",
Optional: true,
},
"owner_user_id": schema.Int64Attribute{
MarkdownDescription: "The user ID to transfer ownership to.",
Optional: true,
},
},
}
}
func (r *ServerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
c, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
r.client = c
}
func (r *ServerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan ServerResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
// Build the create request from plan values.
createReq := client.ServerCreateRequest{
PackageID: plan.PackageID.ValueInt64(),
UserID: plan.UserID.ValueInt64(),
HypervisorID: plan.HypervisorID.ValueInt64(),
}
if !plan.Ipv4.IsNull() && !plan.Ipv4.IsUnknown() {
v := plan.Ipv4.ValueInt64()
createReq.Ipv4 = &v
}
if !plan.Storage.IsNull() && !plan.Storage.IsUnknown() {
v := plan.Storage.ValueInt64()
createReq.Storage = &v
}
if !plan.Memory.IsNull() && !plan.Memory.IsUnknown() {
v := plan.Memory.ValueInt64()
createReq.Memory = &v
}
if !plan.Cores.IsNull() && !plan.Cores.IsUnknown() {
v := plan.Cores.ValueInt64()
createReq.CPUCores = &v
}
if !plan.Traffic.IsNull() && !plan.Traffic.IsUnknown() {
v := plan.Traffic.ValueInt64()
createReq.Traffic = &v
}
if !plan.InboundNetworkSpeed.IsNull() && !plan.InboundNetworkSpeed.IsUnknown() {
v := plan.InboundNetworkSpeed.ValueInt64()
createReq.NetworkSpeedInbound = &v
}
if !plan.OutboundNetworkSpeed.IsNull() && !plan.OutboundNetworkSpeed.IsUnknown() {
v := plan.OutboundNetworkSpeed.ValueInt64()
createReq.NetworkSpeedOutbound = &v
}
if !plan.StorageProfile.IsNull() && !plan.StorageProfile.IsUnknown() {
v := plan.StorageProfile.ValueInt64()
createReq.StorageProfile = &v
}
if !plan.NetworkProfile.IsNull() && !plan.NetworkProfile.IsUnknown() {
v := plan.NetworkProfile.ValueInt64()
createReq.NetworkProfile = &v
}
if !plan.DryRun.IsNull() && !plan.DryRun.IsUnknown() {
v := plan.DryRun.ValueBool()
createReq.DryRun = &v
}
if !plan.AdditionalStorage1.IsNull() && !plan.AdditionalStorage1.IsUnknown() {
v := plan.AdditionalStorage1.ValueInt64()
createReq.AdditionalStorage1 = &v
}
if !plan.AdditionalStorage1Profile.IsNull() && !plan.AdditionalStorage1Profile.IsUnknown() {
v := plan.AdditionalStorage1Profile.ValueInt64()
createReq.AdditionalStorage1Profile = &v
}
if !plan.AdditionalStorage2.IsNull() && !plan.AdditionalStorage2.IsUnknown() {
v := plan.AdditionalStorage2.ValueInt64()
createReq.AdditionalStorage2 = &v
}
if !plan.AdditionalStorage2Profile.IsNull() && !plan.AdditionalStorage2Profile.IsUnknown() {
v := plan.AdditionalStorage2Profile.ValueInt64()
createReq.AdditionalStorage2Profile = &v
}
// Create the server.
rawResp, err := r.client.Post(ctx, "/servers", createReq)
if err != nil {
resp.Diagnostics.AddError("Error Creating Server", err.Error())
return
}
// Parse the create response.
var serverResp client.ServerResponse
if err := json.Unmarshal(rawResp, &serverResp); err != nil {
resp.Diagnostics.AddError("Error Parsing Server Response", err.Error())
return
}
// Set computed values from the response.
plan.ID = types.Int64Value(serverResp.Data.ID)
plan.UUID = types.StringValue(serverResp.Data.UUID)
plan.Hostname = types.StringValue(serverResp.Data.Hostname)
// If name was not set in the plan, use the name from the API response.
if plan.Name.IsNull() || plan.Name.IsUnknown() {
plan.Name = types.StringValue(serverResp.Data.Name)
}
// After creation, apply update-only attributes if they are set.
serverID := serverResp.Data.ID
// Apply name if explicitly set in the plan.
if !plan.Name.IsNull() && !plan.Name.IsUnknown() && plan.Name.ValueString() != serverResp.Data.Name {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/name", serverID), client.ServerModifyNameRequest{
Name: plan.Name.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Error Setting Server Name", err.Error())
return
}
}
// Apply CPU throttle if set.
if !plan.CPUThrottle.IsNull() && !plan.CPUThrottle.IsUnknown() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/cpuThrottle", serverID), client.ServerModifyCPUThrottleRequest{
Percentage: plan.CPUThrottle.ValueInt64(),
})
if err != nil {
resp.Diagnostics.AddError("Error Setting CPU Throttle", err.Error())
return
}
}
// Apply VNC if set.
if !plan.VNCEnabled.IsNull() && !plan.VNCEnabled.IsUnknown() && plan.VNCEnabled.ValueBool() {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/vnc", serverID), nil)
if err != nil {
resp.Diagnostics.AddError("Error Enabling VNC", err.Error())
return
}
}
// Apply suspended if set to true.
if !plan.Suspended.IsNull() && !plan.Suspended.IsUnknown() && plan.Suspended.ValueBool() {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/suspend", serverID), nil)
if err != nil {
resp.Diagnostics.AddError("Error Suspending Server", err.Error())
return
}
}
// Apply backup plan if set.
if !plan.BackupPlanID.IsNull() && !plan.BackupPlanID.IsUnknown() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/backups/plan/%d", serverID, plan.BackupPlanID.ValueInt64()), nil)
if err != nil {
resp.Diagnostics.AddError("Error Setting Backup Plan", err.Error())
return
}
}
// Apply custom XML if set.
if !plan.CustomXML.IsNull() && !plan.CustomXML.IsUnknown() {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/customXML", serverID), client.ServerCustomXMLRequest{
XML: plan.CustomXML.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Error Setting Custom XML", err.Error())
return
}
}
// Apply owner change if set and different from the creating user.
if !plan.OwnerUserID.IsNull() && !plan.OwnerUserID.IsUnknown() && plan.OwnerUserID.ValueInt64() != plan.UserID.ValueInt64() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/owner/%d", serverID, plan.OwnerUserID.ValueInt64()), nil)
if err != nil {
resp.Diagnostics.AddError("Error Changing Server Owner", err.Error())
return
}
}
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *ServerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state ServerResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
rawResp, err := r.client.Get(ctx, fmt.Sprintf("/servers/%d", state.ID.ValueInt64()))
if err != nil {
var apiErr *client.APIError
if errors.As(err, &apiErr) && apiErr.IsNotFound() {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("Error Reading Server", err.Error())
return
}
var serverResp client.ServerResponse
if err := json.Unmarshal(rawResp, &serverResp); err != nil {
resp.Diagnostics.AddError("Error Parsing Server Response", err.Error())
return
}
// Map API response to state model.
s := serverResp.Data
state.ID = types.Int64Value(s.ID)
state.UUID = types.StringValue(s.UUID)
state.Hostname = types.StringValue(s.Hostname)
state.Name = types.StringValue(s.Name)
state.HypervisorID = types.Int64Value(s.HypervisorID)
// Map optional create attributes from the nested API response if they were set in state.
if !state.Storage.IsNull() && s.Settings != nil && s.Settings.Resources != nil {
state.Storage = types.Int64Value(s.Settings.Resources.Storage)
}
if !state.Memory.IsNull() && s.Settings != nil && s.Settings.Resources != nil {
state.Memory = types.Int64Value(s.Settings.Resources.Memory)
}
if !state.Cores.IsNull() && s.CPU != nil {
state.Cores = types.Int64Value(s.CPU.Cores)
}
if !state.Traffic.IsNull() && s.Settings != nil && s.Settings.Resources != nil {
state.Traffic = types.Int64Value(s.Settings.Resources.Traffic)
}
// NetworkSpeed and profiles are not returned by the detail API; preserve from state.
if !state.Suspended.IsNull() {
state.Suspended = types.BoolValue(s.Suspended)
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *ServerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state ServerResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
serverID := state.ID.ValueInt64()
// Preserve computed values from state.
plan.ID = state.ID
plan.UUID = state.UUID
plan.Hostname = state.Hostname
// Name change.
if !plan.Name.Equal(state.Name) {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/name", serverID), client.ServerModifyNameRequest{
Name: plan.Name.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Error Modifying Server Name", err.Error())
return
}
}
// CPU cores change.
if !plan.Cores.Equal(state.Cores) && !plan.Cores.IsNull() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/cpuCores", serverID), client.ServerModifyCPURequest{
CPUCores: plan.Cores.ValueInt64(),
})
if err != nil {
resp.Diagnostics.AddError("Error Modifying Server CPU Cores", err.Error())
return
}
}
// Memory change.
if !plan.Memory.Equal(state.Memory) && !plan.Memory.IsNull() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/memory", serverID), client.ServerModifyMemoryRequest{
Memory: plan.Memory.ValueInt64(),
})
if err != nil {
resp.Diagnostics.AddError("Error Modifying Server Memory", err.Error())
return
}
}
// Traffic change.
if !plan.Traffic.Equal(state.Traffic) && !plan.Traffic.IsNull() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/traffic", serverID), client.ServerModifyTrafficRequest{
Traffic: plan.Traffic.ValueInt64(),
})
if err != nil {
resp.Diagnostics.AddError("Error Modifying Server Traffic", err.Error())
return
}
}
// CPU throttle change.
if !plan.CPUThrottle.Equal(state.CPUThrottle) && !plan.CPUThrottle.IsNull() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/modify/cpuThrottle", serverID), client.ServerModifyCPUThrottleRequest{
Percentage: plan.CPUThrottle.ValueInt64(),
})
if err != nil {
resp.Diagnostics.AddError("Error Modifying CPU Throttle", err.Error())
return
}
}
// VNC toggle.
if !plan.VNCEnabled.Equal(state.VNCEnabled) && !plan.VNCEnabled.IsNull() {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/vnc", serverID), nil)
if err != nil {
resp.Diagnostics.AddError("Error Toggling VNC", err.Error())
return
}
}
// Suspended change.
if !plan.Suspended.Equal(state.Suspended) && !plan.Suspended.IsNull() {
if plan.Suspended.ValueBool() {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/suspend", serverID), nil)
if err != nil {
resp.Diagnostics.AddError("Error Suspending Server", err.Error())
return
}
} else {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/unsuspend", serverID), nil)
if err != nil {
resp.Diagnostics.AddError("Error Unsuspending Server", err.Error())
return
}
}
}
// Backup plan change.
if !plan.BackupPlanID.Equal(state.BackupPlanID) {
planID := int64(0)
if !plan.BackupPlanID.IsNull() {
planID = plan.BackupPlanID.ValueInt64()
}
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/backups/plan/%d", serverID, planID), nil)
if err != nil {
resp.Diagnostics.AddError("Error Modifying Backup Plan", err.Error())
return
}
}
// Custom XML change.
if !plan.CustomXML.Equal(state.CustomXML) && !plan.CustomXML.IsNull() {
_, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/customXML", serverID), client.ServerCustomXMLRequest{
XML: plan.CustomXML.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Error Setting Custom XML", err.Error())
return
}
}
// Owner change.
if !plan.OwnerUserID.Equal(state.OwnerUserID) && !plan.OwnerUserID.IsNull() {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/owner/%d", serverID, plan.OwnerUserID.ValueInt64()), nil)
if err != nil {
resp.Diagnostics.AddError("Error Changing Server Owner", err.Error())
return
}
}
// Package change.
if !plan.PackageID.Equal(state.PackageID) {
_, err := r.client.Put(ctx, fmt.Sprintf("/servers/%d/package/%d", serverID, plan.PackageID.ValueInt64()), nil)
if err != nil {
resp.Diagnostics.AddError("Error Changing Server Package", err.Error())
return
}
}
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *ServerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state ServerResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
_, err := r.client.Delete(ctx, fmt.Sprintf("/servers/%d?delay=0", state.ID.ValueInt64()))
if err != nil {
var apiErr *client.APIError
if errors.As(err, &apiErr) && apiErr.IsNotFound() {
// Already deleted, nothing to do.
return
}
resp.Diagnostics.AddError("Error Deleting Server", err.Error())
return
}
}
func (r *ServerResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Invalid Import ID",
fmt.Sprintf("Could not parse server ID %q as an integer: %s", req.ID, err),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), types.Int64Value(id))...)
}