diff --git a/docs/resources/vm.md b/docs/resources/vm.md
index a5d16d5..6999ad8 100644
--- a/docs/resources/vm.md
+++ b/docs/resources/vm.md
@@ -67,6 +67,7 @@ resource "hypercore_vm" "myvm" {
ssh_authorized_keys = "",
ssh_import_id = "",
})
+ preserve_mac_address = true # User wants to preserve mac address from the source machine (Default is false)
}
}
@@ -102,7 +103,7 @@ output "vm_uuid" {
### Optional
- `affinity_strategy` (Object) VM node affinity. (see [below for nested schema](#nestedatt--affinity_strategy))
-- `clone` (Object) Clone options if the VM is being created as a clone. The `source_vm_uuid` is the UUID of the VM used for cloning,
`user_data` and `meta_data` are used for the cloud init data. (see [below for nested schema](#nestedatt--clone))
+- `clone` (Attributes) Clone options if the VM is being created as a clone. The `source_vm_uuid` is the UUID of the VM used for cloning,
`user_data` and `meta_data` are used for the cloud init data. (see [below for nested schema](#nestedatt--clone))
- `description` (String) Description of this VM
- `import` (Attributes) Options for importing a VM through a SMB server or some other HTTP location.
Use server, username, password for SMB or http_uri for some other HTTP location. Parameters path and file_name are always **required** (see [below for nested schema](#nestedatt--import))
- `memory` (Number) Memory (RAM) size in `MiB`: If the cloned VM was already created
and it's memory was modified, the cloned VM will be rebooted (either gracefully or forcefully)
@@ -127,10 +128,14 @@ Optional:
### Nested Schema for `clone`
+Required:
+
+- `source_vm_uuid` (String)
+
Optional:
- `meta_data` (String)
-- `source_vm_uuid` (String)
+- `preserve_mac_address` (Boolean)
- `user_data` (String)
diff --git a/examples/resources/hypercore_vm/resource.tf b/examples/resources/hypercore_vm/resource.tf
index cc30b8a..aded50a 100644
--- a/examples/resources/hypercore_vm/resource.tf
+++ b/examples/resources/hypercore_vm/resource.tf
@@ -36,6 +36,7 @@ resource "hypercore_vm" "myvm" {
ssh_authorized_keys = "",
ssh_import_id = "",
})
+ preserve_mac_address = true # User wants to preserve mac address from the source machine (Default is false)
}
}
diff --git a/internal/provider/hypercore_vm_resource.go b/internal/provider/hypercore_vm_resource.go
index 9d391d8..d30392c 100644
--- a/internal/provider/hypercore_vm_resource.go
+++ b/internal/provider/hypercore_vm_resource.go
@@ -16,6 +16,7 @@ import (
"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/objectdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
@@ -62,9 +63,10 @@ type ImportModel struct {
}
type CloneModel struct {
- SourceVMUUID types.String `tfsdk:"source_vm_uuid"`
- UserData types.String `tfsdk:"user_data"`
- MetaData types.String `tfsdk:"meta_data"`
+ SourceVMUUID types.String `tfsdk:"source_vm_uuid"`
+ UserData types.String `tfsdk:"user_data"`
+ MetaData types.String `tfsdk:"meta_data"`
+ PreserveMacAddress types.Bool `tfsdk:"preserve_mac_address"`
}
type AffinityStrategyModel struct {
@@ -149,15 +151,26 @@ The provider will currently try to shutdown VM only before VM delete.`,
},
},
},
- "clone": schema.ObjectAttribute{
+ "clone": schema.SingleNestedAttribute{
MarkdownDescription: "" +
"Clone options if the VM is being created as a clone. The `source_vm_uuid` is the UUID of the VM used for cloning,
" +
"`user_data` and `meta_data` are used for the cloud init data.",
Optional: true,
- AttributeTypes: map[string]attr.Type{
- "source_vm_uuid": types.StringType,
- "user_data": types.StringType,
- "meta_data": types.StringType,
+ Attributes: map[string]schema.Attribute{
+ "source_vm_uuid": schema.StringAttribute{
+ Required: true,
+ },
+ "user_data": schema.StringAttribute{
+ Optional: true,
+ },
+ "meta_data": schema.StringAttribute{
+ Optional: true,
+ },
+ "preserve_mac_address": schema.BoolAttribute{
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(false),
+ },
},
},
"affinity_strategy": schema.ObjectAttribute{
@@ -219,16 +232,19 @@ func (r *HypercoreVMResource) Configure(ctx context.Context, req resource.Config
func getVMStruct(data *HypercoreVMResourceModel, vmDescription *string, vmTags *[]string) *utils.VM {
// Gets VM structure from Utils.VM, sends parameters based on which VM create logic is being called
sourceVMUUID, userData, metaData := "", "", ""
+ preserveMacAddress := false
if data.Clone != nil {
sourceVMUUID = data.Clone.SourceVMUUID.ValueString()
userData = data.Clone.UserData.ValueString()
metaData = data.Clone.MetaData.ValueString()
+ preserveMacAddress = data.Clone.PreserveMacAddress.ValueBool()
}
vmStruct := utils.GetVMStruct(
data.Name.ValueString(),
sourceVMUUID,
userData,
metaData,
+ preserveMacAddress,
vmDescription,
vmTags,
data.VCPU.ValueInt32Pointer(),
diff --git a/internal/provider/tests/acceptance/hypercore_vm_resource_clone_acc_test.go b/internal/provider/tests/acceptance/hypercore_vm_resource_clone_acc_test.go
index 5777ed5..ad8d9bd 100644
--- a/internal/provider/tests/acceptance/hypercore_vm_resource_clone_acc_test.go
+++ b/internal/provider/tests/acceptance/hypercore_vm_resource_clone_acc_test.go
@@ -125,6 +125,7 @@ resource "hypercore_vm" "test" {
source_vm_uuid = %[2]q
user_data = ""
meta_data = ""
+ preserve_mac_address = false
}
}
`, vm_name, source_vm_uuid, requested_power_state)
diff --git a/internal/utils/vm.go b/internal/utils/vm.go
index eabba4d..292cb5f 100644
--- a/internal/utils/vm.go
+++ b/internal/utils/vm.go
@@ -84,6 +84,7 @@ func GetVMStruct(
_sourceVMUUID string,
userData string,
metaData string,
+ preserveMacAddress bool,
_description *string,
_tags *[]string,
_vcpu *int32,
@@ -101,7 +102,7 @@ func GetVMStruct(
UUID: "",
VMName: _VMName,
sourceVMUUID: _sourceVMUUID,
- preserveMacAddress: false,
+ preserveMacAddress: preserveMacAddress,
cloudInit: map[string]any{
"userData": userDataB64,
"metaData": metaDataB64,
@@ -146,8 +147,8 @@ func (vc *VM) SendFromScratchRequest(restClient RestClient) *TaskTag {
return taskTag
}
-func (vc *VM) FromScratch(restClient RestClient, ctx context.Context) (bool, string) {
- task := vc.SendFromScratchRequest(restClient)
+func (vmNew *VM) FromScratch(restClient RestClient, ctx context.Context) (bool, string) {
+ task := vmNew.SendFromScratchRequest(restClient)
task.WaitTask(restClient, ctx)
taskStatus := task.GetStatus(restClient)
@@ -156,22 +157,42 @@ func (vc *VM) FromScratch(restClient RestClient, ctx context.Context) (bool, str
}
if state, ok := (*taskStatus)["state"]; ok && state == "COMPLETE" {
- vc.UUID = task.CreatedUUID
- return true, fmt.Sprintf("Virtual machine create complete to - %s.", vc.VMName)
+ vmNew.UUID = task.CreatedUUID
+ return true, fmt.Sprintf("Virtual machine create complete to - %s.", vmNew.VMName)
}
panic("There was a problem during VM create.")
}
-func (vc *VM) SendCloneRequest(restClient RestClient, sourceVM map[string]any) *TaskTag {
- // Clone payload
+func (vmNew *VM) SendCloneRequest(restClient RestClient, sourceVM map[string]any) *TaskTag {
clonePayload := map[string]any{
"template": map[string]any{
- "name": vc.VMName,
- "cloudInitData": vc.cloudInit,
+ "name": vmNew.VMName,
+ "cloudInitData": vmNew.cloudInit,
},
}
+ // User wants to preserve net devices from the source VM
+ if vmNew.preserveMacAddress {
+ netDevicesNewVM := []map[string]any{}
+
+ if sourceNetDevs, ok := sourceVM["netDevs"].([]any); ok {
+ for _, netDeviceSource := range sourceNetDevs {
+ // Safely assert that each item is a map
+ if device, ok := netDeviceSource.(map[string]any); ok {
+ netDevicesNewVM = append(netDevicesNewVM, map[string]any{
+ "type": device["type"],
+ "macAddress": device["macAddress"],
+ "vlan": device["vlan"],
+ })
+ }
+ }
+ }
+ // Safely assert the "template" field is a map
+ if tmpl, ok := clonePayload["template"].(map[string]any); ok {
+ tmpl["netDevs"] = netDevicesNewVM
+ }
+ }
taskTag, _, _ := restClient.CreateRecord(
fmt.Sprintf("/rest/v1/VirDomain/%s/clone", sourceVM["uuid"]),
clonePayload,
@@ -180,33 +201,33 @@ func (vc *VM) SendCloneRequest(restClient RestClient, sourceVM map[string]any) *
return taskTag
}
-func (vc *VM) Clone(restClient RestClient, ctx context.Context) (bool, string) {
- vm := GetVM(map[string]any{"name": vc.VMName}, restClient)
+func (vmNew *VM) Clone(restClient RestClient, ctx context.Context) (bool, string) {
+ vm := GetVM(map[string]any{"name": vmNew.VMName}, restClient)
if len(vm) > 0 {
- vc.UUID = AnyToString(vm[0]["uuid"])
- return false, fmt.Sprintf("Virtual machine %s already exists.", vc.VMName)
+ vmNew.UUID = AnyToString(vm[0]["uuid"])
+ return false, fmt.Sprintf("Virtual machine %s already exists.", vmNew.VMName)
}
sourceVM := GetOneVM(
- vc.sourceVMUUID,
+ vmNew.sourceVMUUID,
restClient,
)
sourceVMName, _ := sourceVM["name"].(string)
// Clone payload
- task := vc.SendCloneRequest(restClient, sourceVM)
+ task := vmNew.SendCloneRequest(restClient, sourceVM)
task.WaitTask(restClient, ctx)
taskStatus := task.GetStatus(restClient)
if taskStatus != nil {
if state, ok := (*taskStatus)["state"]; ok && state == "COMPLETE" {
- vc.UUID = task.CreatedUUID
- return true, fmt.Sprintf("Virtual machine - %s %s - cloning complete to - %s.", sourceVMName, vc.sourceVMUUID, vc.VMName)
+ vmNew.UUID = task.CreatedUUID
+ return true, fmt.Sprintf("Virtual machine - %s %s - cloning complete to - %s.", sourceVMName, vmNew.sourceVMUUID, vmNew.VMName)
}
}
- panic(fmt.Sprintf("There was a problem during cloning of %s %s, cloning failed.", sourceVMName, vc.sourceVMUUID))
+ panic(fmt.Sprintf("There was a problem during cloning of %s %s, cloning failed.", sourceVMName, vmNew.sourceVMUUID))
}
func (vc *VM) SendImportRequest(restClient RestClient, source map[string]any) *TaskTag {