Skip to content

Add VM import (based on HC3 API) to the hypercore_vm_resource #26

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

Closed
wants to merge 3 commits into from
Closed
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
71 changes: 65 additions & 6 deletions internal/provider/hypercore_vm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,22 @@
Description types.String `tfsdk:"description"`
VCPU types.Int32 `tfsdk:"vcpu"`
Memory types.Int64 `tfsdk:"memory"`
Import ImportModel `tfsdk:"import"`
SnapshotScheduleUUID types.String `tfsdk:"snapshot_schedule_uuid"`
Clone CloneModel `tfsdk:"clone"`
AffinityStrategy AffinityStrategyModel `tfsdk:"affinity_strategy"`
Id types.String `tfsdk:"id"`
}

type ImportModel struct {
HTTPUri types.String `tfsdk:"http_uri"`
Server types.String `tfsdk:"server"`
Username types.String `tfsdk:"username"`
Password types.String `tfsdk:"password"`
Path types.String `tfsdk:"path"`
FileName types.String `tfsdk:"file_name"`
}

type CloneModel struct {
SourceVMUUID types.String `tfsdk:"source_vm_uuid"`
UserData types.String `tfsdk:"user_data"`
Expand Down Expand Up @@ -95,6 +105,19 @@
MarkdownDescription: "UUID of the snapshot schedule to create automatic snapshots",
Optional: true,
},
"import": schema.ObjectAttribute{
MarkdownDescription: "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. Paramaters path and file_name are always **required**",

Check failure on line 110 in internal/provider/hypercore_vm_resource.go

View workflow job for this annotation

GitHub Actions / Build

`Paramaters` is a misspelling of `Parameters` (misspell)

Check failure on line 110 in internal/provider/hypercore_vm_resource.go

View workflow job for this annotation

GitHub Actions / Build

`Paramaters` is a misspelling of `Parameters` (misspell)
Optional: true,
AttributeTypes: map[string]attr.Type{
"http_uri": types.StringType,
"server": types.StringType,
"username": types.StringType,
"password": types.StringType,
"path": types.StringType,
"file_name": types.StringType,
},
},
"clone": schema.ObjectAttribute{
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>" +
Expand Down Expand Up @@ -203,6 +226,7 @@

tflog.Info(ctx, fmt.Sprintf("TTRT Create: name=%s, source_uuid=%s", data.Name.ValueString(), data.Clone.SourceVMUUID.ValueString()))

// Import or clone
vmClone, _ := utils.NewVM(
data.Name.ValueString(),
data.Clone.SourceVMUUID.ValueString(),
Expand All @@ -218,13 +242,48 @@
data.AffinityStrategy.PreferredNodeUUID.ValueString(),
data.AffinityStrategy.BackupNodeUUID.ValueString(),
)
changed, msg := vmClone.Create(*r.client, ctx)
tflog.Info(ctx, fmt.Sprintf("Changed: %t, Message: %s\n", changed, msg))

// General parametrization
// set: description, group, vcpu, memory, power_state
changed, vmWasRebooted, vmDiff := vmClone.SetVMParams(*r.client, ctx)
tflog.Info(ctx, fmt.Sprintf("Changed: %t, Was VM Rebooted: %t, Diff: %v", changed, vmWasRebooted, vmDiff))
// http import
httpUri := data.Import.HTTPUri.ValueString()

// smb import
smbServer := data.Import.Server.ValueString()
smbUsername := data.Import.Username.ValueString()
smbPassword := data.Import.Password.ValueString()

// location
path := data.Import.Path.ValueString()
fileName := data.Import.FileName.ValueString()

isSMBImport := smbServer != "" || smbUsername != "" || smbPassword != ""
isHTTPImport := httpUri != ""

if isHTTPImport && !isSMBImport {
nameDiag := utils.ValidateHTTP(httpUri, path)
if nameDiag != nil {
resp.Diagnostics.AddError(nameDiag.Summary(), nameDiag.Detail())
return
}
httpSource := utils.BuildHTTPImportSource(httpUri, path, fileName)
vmClone.ImportVM(*r.client, httpSource, ctx)
} else if isSMBImport && !isHTTPImport {
nameDiag := utils.ValidateSMB(smbServer, smbUsername, smbPassword, path)
if nameDiag != nil {
resp.Diagnostics.AddError(nameDiag.Summary(), nameDiag.Detail())
return
}
smbSource := utils.BuildSMBImportSource(smbUsername, smbPassword, smbServer, path, fileName)
vmClone.ImportVM(*r.client, smbSource, ctx)
} else {
// Cloning
changed, msg := vmClone.Create(*r.client, ctx)
tflog.Info(ctx, fmt.Sprintf("Changed: %t, Message: %s\n", changed, msg))

// General parametrization
// set: description, group, vcpu, memory, power_state
changed, vmWasRebooted, vmDiff := vmClone.SetVMParams(*r.client, ctx)
tflog.Info(ctx, fmt.Sprintf("Changed: %t, Was VM Rebooted: %t, Diff: %v", changed, vmWasRebooted, vmDiff))
}

// save into the Terraform state.
data.Id = types.StringValue(vmClone.UUID)
Expand Down
65 changes: 49 additions & 16 deletions internal/utils/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
"net/http"
"os"
"strconv"
"strings"

"github.com/hashicorp/terraform-plugin-framework/diag"
)

func isSuperset(superset map[string]any, candidate map[string]any) bool {
Expand Down Expand Up @@ -83,21 +86,6 @@ func filterResultsRecursive(results []map[string]any, filterData map[string]any)
return filtered
}

// nolint:unused
func filterMap(input map[string]any, fieldNames ...string) map[string]any {
output := map[string]any{}

for _, fieldName := range fieldNames {
if value, ok := input[fieldName]; ok {
if value != nil || value != "" {
output[fieldName] = value
}
}
}

return output
}

func jsonObjectToTaskTag(jsonObj any) *TaskTag {
var taskTag *TaskTag

Expand Down Expand Up @@ -247,7 +235,7 @@ func AnyToListOfStrings(list any) []string {
func ReadLocalFileBinary(filePath string) ([]byte, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("Error opening file '%s': %s", filePath, err)
return nil, fmt.Errorf("error opening file '%s': %s", filePath, err)
}
defer file.Close()

Expand Down Expand Up @@ -303,3 +291,48 @@ func GetFileSize(sourceFilePath string) int64 {
}
return fileInfo.Size()
}

func ValidateSMB(server string, username string, password string, path string) diag.Diagnostic {
if server == "" {
return diag.NewErrorDiagnostic(
"Missing 'server' parameter",
"For using SMB, you must specify the 'server' parameter",
)
}
if username == "" {
return diag.NewErrorDiagnostic(
"Missing 'username' parameter",
"For using SMB, you must specify the 'username' parameter",
)
}
if password == "" {
return diag.NewErrorDiagnostic(
"Missing 'password' parameter",
"For using SMB, you must specify the 'password' parameter",
)
}
if path == "" {
return diag.NewErrorDiagnostic(
"Missing 'path' parameter",
"For using SMB, you must specify the 'path' parameter",
)
}
return nil
}

func ValidateHTTP(httpUri string, path string) diag.Diagnostic {
if !strings.HasPrefix(httpUri, "http://") && !strings.HasPrefix(httpUri, "https://") {
return diag.NewErrorDiagnostic(
"Invalid HTTP uri",
"Invalid HTTP uri. Uri must start with 'http://' or 'https://'",
)
}
if path == "" {
return diag.NewErrorDiagnostic(
"Invalid path",
"Invalid path. Path parameter must be defined and start with '/'",
)
}

return nil
}
Loading
Loading