Skip to content

Preserve mac address logic for VM clone #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/resources/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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, <br>`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, <br>`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. <br>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 <br>and it's memory was modified, the cloned VM will be rebooted (either gracefully or forcefully)
Expand All @@ -127,10 +128,14 @@ Optional:
<a id="nestedatt--clone"></a>
### Nested Schema for `clone`

Required:

- `source_vm_uuid` (String)

Optional:

- `meta_data` (String)
- `source_vm_uuid` (String)
- `preserve_mac_address` (Boolean)
- `user_data` (String)


Expand Down
1 change: 1 addition & 0 deletions examples/resources/hypercore_vm/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
32 changes: 24 additions & 8 deletions internal/provider/hypercore_vm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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, <br>" +
"`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{
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
57 changes: 39 additions & 18 deletions internal/utils/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func GetVMStruct(
_sourceVMUUID string,
userData string,
metaData string,
preserveMacAddress bool,
_description *string,
_tags *[]string,
_vcpu *int32,
Expand All @@ -101,7 +102,7 @@ func GetVMStruct(
UUID: "",
VMName: _VMName,
sourceVMUUID: _sourceVMUUID,
preserveMacAddress: false,
preserveMacAddress: preserveMacAddress,
cloudInit: map[string]any{
"userData": userDataB64,
"metaData": metaDataB64,
Expand Down Expand Up @@ -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)

Expand All @@ -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,
Expand All @@ -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 {
Expand Down
Loading