diff --git a/docs/resources/vm.md b/docs/resources/vm.md index 79f46fa..a5d16d5 100644 --- a/docs/resources/vm.md +++ b/docs/resources/vm.md @@ -36,7 +36,7 @@ locals { } resource "hypercore_vm" "empty-vm" { - group = "my-group" + tags = ["my-group"] name = "empty-vm" description = "some description" @@ -49,7 +49,7 @@ data "hypercore_vms" "clone_source_vm" { } resource "hypercore_vm" "myvm" { - group = "my-group" + tags = ["my-group"] name = local.vm_name description = "some description" @@ -71,7 +71,7 @@ resource "hypercore_vm" "myvm" { } resource "hypercore_vm" "import-from-smb" { - group = "my-group" + tags = ["my-group"] name = "imported-vm" description = "some description" @@ -104,10 +104,10 @@ output "vm_uuid" { - `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)) - `description` (String) Description of this VM -- `group` (String) Group/tag to create this VM in - `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) - `snapshot_schedule_uuid` (String) UUID of the snapshot schedule to create automatic snapshots +- `tags` (List of String) List of tags to create this VM in - `vcpu` (Number) Number of CPUs on this VM. If the cloned VM was already created and it's
`VCPU` was modified, the cloned VM will be rebooted (either gracefully or forcefully) ### Read-Only diff --git a/examples/resources/hypercore_vm/resource.tf b/examples/resources/hypercore_vm/resource.tf index 900b614..cc30b8a 100644 --- a/examples/resources/hypercore_vm/resource.tf +++ b/examples/resources/hypercore_vm/resource.tf @@ -5,7 +5,7 @@ locals { } resource "hypercore_vm" "empty-vm" { - group = "my-group" + tags = ["my-group"] name = "empty-vm" description = "some description" @@ -18,7 +18,7 @@ data "hypercore_vms" "clone_source_vm" { } resource "hypercore_vm" "myvm" { - group = "my-group" + tags = ["my-group"] name = local.vm_name description = "some description" @@ -40,7 +40,7 @@ resource "hypercore_vm" "myvm" { } resource "hypercore_vm" "import-from-smb" { - group = "my-group" + tags = ["my-group"] name = "imported-vm" description = "some description" diff --git a/internal/provider/hypercore_vm_resource.go b/internal/provider/hypercore_vm_resource.go index d82da03..9d391d8 100644 --- a/internal/provider/hypercore_vm_resource.go +++ b/internal/provider/hypercore_vm_resource.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "strconv" + "strings" "time" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -39,7 +40,7 @@ type HypercoreVMResource struct { // HypercoreVMResourceModel describes the resource data model. type HypercoreVMResourceModel struct { - Group types.String `tfsdk:"group"` + Tags types.List `tfsdk:"tags"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` VCPU types.Int32 `tfsdk:"vcpu"` @@ -91,8 +92,9 @@ HC_VM_SHUTDOWN_TIMEOUT can be changed via environ. The provider will currently try to shutdown VM only before VM delete.`, Attributes: map[string]schema.Attribute{ - "group": schema.StringAttribute{ - MarkdownDescription: "Group/tag to create this VM in", + "tags": schema.ListAttribute{ + ElementType: types.StringType, + MarkdownDescription: "List of tags to create this VM in", Optional: true, }, "name": schema.StringAttribute{ @@ -240,23 +242,20 @@ func getVMStruct(data *HypercoreVMResourceModel, vmDescription *string, vmTags * return vmStruct } -func validateParameters(data *HypercoreVMResourceModel) (*string, *[]string) { +func validateParameters(data *HypercoreVMResourceModel, ctx context.Context) (*string, *[]string, diag.Diagnostics) { var tags *[]string var description *string - if data.Group.ValueString() == "" { - tags = nil - } else { - t := []string{data.Group.ValueString()} - tags = &t - } + tflog.Debug(ctx, "TTRT Loading tags") + diags := data.Tags.ElementsAs(ctx, &tags, false) + tflog.Debug(ctx, fmt.Sprintf("TTRT Tags: %v", tags)) if data.Description.ValueString() == "" { description = nil } else { description = data.Description.ValueStringPointer() } - return description, tags + return description, tags, diags } func isHTTPImport(data *HypercoreVMResourceModel) bool { // Check if HTTP URI is being used for VM import @@ -285,6 +284,7 @@ func (r *HypercoreVMResource) handleCloneLogic(data *HypercoreVMResourceModel, c data.Id = types.StringValue(vmNew.UUID) tflog.Info(ctx, fmt.Sprintf("Changed: %t, Was VM Rebooted: %t, Diff: %v", changed, vmWasRebooted, vmDiff)) } + func (r *HypercoreVMResource) handleImportFromSMBLogic(data *HypercoreVMResourceModel, ctx context.Context, resp *resource.CreateResponse, vmNew *utils.VM, path string, fileName string) { smbServer, smbUsername, smbPassword := data.Import.Server.ValueString(), data.Import.Username.ValueString(), data.Import.Password.ValueString() errorDiagnostic := utils.ValidateSMB(smbServer, smbUsername, smbPassword) @@ -299,6 +299,7 @@ func (r *HypercoreVMResource) handleImportFromSMBLogic(data *HypercoreVMResource data.Id = types.StringValue(vmNew.UUID) tflog.Info(ctx, fmt.Sprintf("Changed: %t, Was VM Rebooted: %t, Diff: %v", changed, vmWasRebooted, vmDiff)) } + func (r *HypercoreVMResource) handleImportFromURILogic(data *HypercoreVMResourceModel, ctx context.Context, resp *resource.CreateResponse, vmNew *utils.VM, path string, fileName string) { httpUri := data.Import.HTTPUri.ValueString() errorDiagnostic := utils.ValidateHTTP(httpUri, path) @@ -313,6 +314,7 @@ func (r *HypercoreVMResource) handleImportFromURILogic(data *HypercoreVMResource data.Id = types.StringValue(vmNew.UUID) tflog.Info(ctx, fmt.Sprintf("Changed: %t, Was VM Rebooted: %t, Diff: %v", changed, vmWasRebooted, vmDiff)) } + func (r *HypercoreVMResource) doCreateLogic(data *HypercoreVMResourceModel, ctx context.Context, resp *resource.CreateResponse, description *string, tags *[]string) { vmNew := getVMStruct(data, description, tags) // Chose which VM create logic we're going with (clone, import, from scratch) @@ -354,7 +356,11 @@ func (r *HypercoreVMResource) Create(ctx context.Context, req resource.CreateReq } // Validate parameters TODO: Add other inputs here from schema if validation is needed - description, tags := validateParameters(&data) + description, tags, diags := validateParameters(&data, ctx) + if diags.HasError() { + resp.Diagnostics.Append(diags.Errors()...) + return + } // Right now handles import or clone TODO: Add other VM create options here r.doCreateLogic(&data, ctx, resp, description, tags) @@ -398,7 +404,23 @@ func (r *HypercoreVMResource) Read(ctx context.Context, req resource.ReadRequest data.Name = types.StringValue(utils.AnyToString(hc3_vm["name"])) data.Description = types.StringValue(utils.AnyToString(hc3_vm["description"])) - // data.Group TODO - replace "group" string with "tags" list of strings + + // Read tags from the hc3 api + // - hc3Tags = "tag1,tag2,..." + // - tfTags = hc3Tags -> ["tag1", "tag2", ...] + hc3Tags := utils.AnyToString(hc3_vm["tags"]) + goTagsList := strings.Split(hc3Tags, ",") + tfTagsValues := make([]attr.Value, len(goTagsList)) + for i, tag := range goTagsList { + tfTagsValues[i] = types.StringValue(tag) + } + + var diags diag.Diagnostics + data.Tags, diags = types.ListValue(types.StringType, tfTagsValues) + if diags.HasError() { + resp.Diagnostics.Append(diags.Errors()...) + return + } // NOTE: power state not needed here anymore because of the hypercore_vm_power_state resource // hc3_power_state := utils.AnyToString(hc3_vm["state"]) @@ -494,7 +516,16 @@ func (r *HypercoreVMResource) Update(ctx context.Context, req resource.UpdateReq updatePayload["affinityStrategy"] = affinityStrategy } - taskTag, _ := restClient.UpdateRecord( /**/ + // update tags: ["tag1", "tag2", ...] -> "tag1,tag2,..." + var tagsList []string + diags := data.Tags.ElementsAs(ctx, &tagsList, false) + if diags.HasError() { + resp.Diagnostics.Append(diags.Errors()...) + return + } + updatePayload["tags"] = utils.TagsListToCommaString(tagsList) + + taskTag, _ := restClient.UpdateRecord( fmt.Sprintf("/rest/v1/VirDomain/%s", vm_uuid), updatePayload, -1, diff --git a/internal/provider/tests/acceptance/hypercore_vm_resource__snapshot_schedule__acc_test.go b/internal/provider/tests/acceptance/hypercore_vm_resource__snapshot_schedule__acc_test.go index 14d2a76..7d9d155 100644 --- a/internal/provider/tests/acceptance/hypercore_vm_resource__snapshot_schedule__acc_test.go +++ b/internal/provider/tests/acceptance/hypercore_vm_resource__snapshot_schedule__acc_test.go @@ -21,7 +21,8 @@ func TestAccHypercoreVMResourceSnapshotSchedule(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_vm.test", "description", "testtf-vm-description"), resource.TestCheckResourceAttr("hypercore_vm.test", "memory", "4096"), - resource.TestCheckResourceAttr("hypercore_vm.test", "group", "testtf"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.#", "1"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.0", "testtf"), resource.TestCheckNoResourceAttr("hypercore_vm.test", "clone"), resource.TestCheckResourceAttr("hypercore_vm.test", "name", "testtf-vm"), resource.TestCheckResourceAttr("hypercore_vm.test", "vcpu", "4"), @@ -62,7 +63,8 @@ func TestAccHypercoreVMResourceSnapshotSchedule(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_vm.test", "name", "testtf-vm"), resource.TestCheckResourceAttr("hypercore_vm.test", "description", "testtf-vm-description"), - resource.TestCheckResourceAttr("hypercore_vm.test", "group", "testtf"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.#", "1"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.1", "testtf"), resource.TestCheckNoResourceAttr("hypercore_vm.test", "clone"), resource.TestCheckResourceAttr("hypercore_vm.test", "vcpu", "4"), resource.TestCheckResourceAttr("hypercore_vm.test", "memory", "4096"), @@ -92,7 +94,7 @@ func testConfig_NoSnapshotScheduleUUID(vm_name string) string { return fmt.Sprintf(` resource "hypercore_vm" "test" { name = %[1]q - group = "testtf" + tags = ["testtf"] vcpu = 4 memory = 4096 description = "testtf-vm-description" 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 163cadd..5777ed5 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 @@ -23,7 +23,9 @@ func TestAccHypercoreVMResourceClone(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_vm.test", "description", "testtf-vm-description"), resource.TestCheckResourceAttr("hypercore_vm.test", "memory", "4096"), - resource.TestCheckResourceAttr("hypercore_vm.test", "group", "testtf"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.#", "2"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.0", "testtf-tag-1"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.1", "testtf-tag-2"), resource.TestCheckResourceAttr("hypercore_vm.test", "clone.meta_data", ""), resource.TestCheckResourceAttr("hypercore_vm.test", "clone.user_data", ""), resource.TestCheckResourceAttr("hypercore_vm.test", "clone.source_vm_uuid", source_vm_uuid), @@ -69,7 +71,9 @@ func TestAccHypercoreVMResourceClone(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_vm.test", "name", "testtf-vm"), resource.TestCheckResourceAttr("hypercore_vm.test", "description", "testtf-vm-description"), - resource.TestCheckResourceAttr("hypercore_vm.test", "group", "testtf"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.#", "2"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.0", "testtf-tag-1"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.1", "testtf-tag-2"), resource.TestCheckResourceAttr("hypercore_vm.test", "vcpu", "4"), resource.TestCheckResourceAttr("hypercore_vm.test", "memory", "4096"), // resource.TestCheckResourceAttr("hypercore_vm.test", "power_state", requested_power_state), @@ -108,7 +112,10 @@ func testAccHypercoreVMResourceCloneConfig(vm_name string) string { return fmt.Sprintf(` resource "hypercore_vm" "test" { name = %[1]q - group = "testtf" + tags = [ + "testtf-tag-1", + "testtf-tag-2", + ] vcpu = 4 memory = 4096 description = "testtf-vm-description" diff --git a/internal/provider/tests/acceptance/hypercore_vm_resource_import_acc_test.go b/internal/provider/tests/acceptance/hypercore_vm_resource_import_acc_test.go index 517a051..01613ab 100644 --- a/internal/provider/tests/acceptance/hypercore_vm_resource_import_acc_test.go +++ b/internal/provider/tests/acceptance/hypercore_vm_resource_import_acc_test.go @@ -21,7 +21,8 @@ func TestAccHypercoreVMResourceImport(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("hypercore_vm.test", "description", "imported-vm"), resource.TestCheckResourceAttr("hypercore_vm.test", "memory", "4096"), - resource.TestCheckResourceAttr("hypercore_vm.test", "group", "Xlabintegrationtest"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.#", "1"), + resource.TestCheckResourceAttr("hypercore_vm.test", "tags.0", "Xlabintegrationtest"), resource.TestCheckResourceAttr("hypercore_vm.test", "name", "imported_vm_integration"), resource.TestCheckResourceAttr("hypercore_vm.test", "vcpu", "4"), ), @@ -34,7 +35,7 @@ func testAccHypercoreVMResourceImportConfig(vm_name string) string { return fmt.Sprintf(` resource "hypercore_vm" "test" { name = %[1]q - group = "Xlabintegrationtest" + tags = ["Xlabintegrationtest"] vcpu = 4 memory = 4096 description = "imported-vm" diff --git a/internal/utils/helper.go b/internal/utils/helper.go index d9567dd..25057b1 100644 --- a/internal/utils/helper.go +++ b/internal/utils/helper.go @@ -106,7 +106,7 @@ func jsonObjectToTaskTag(jsonObj any) *TaskTag { return taskTag } -func tagsListToCommaString(tags []string) string { +func TagsListToCommaString(tags []string) string { tagsHyp := "" for _, tag := range tags { tagsHyp += tag + "," diff --git a/internal/utils/vm.go b/internal/utils/vm.go index a1f2633..eabba4d 100644 --- a/internal/utils/vm.go +++ b/internal/utils/vm.go @@ -468,7 +468,7 @@ func (vc *VM) BuildUpdatePayload(changedParams map[string]bool) map[string]any { updatePayload["description"] = *vc.description } if changed, ok := changedParams["tags"]; ok && changed { - updatePayload["tags"] = tagsListToCommaString(*vc.tags) + updatePayload["tags"] = TagsListToCommaString(*vc.tags) } if changed, ok := changedParams["memory"]; ok && changed { vcMemoryBytes := *vc.memory * 1024 * 1024 // MB to B @@ -505,7 +505,7 @@ func (vc *VM) BuildImportTemplate() map[string]any { importTemplate["description"] = *vc.description } if vc.tags != nil { - importTemplate["tags"] = tagsListToCommaString(*vc.tags) + importTemplate["tags"] = TagsListToCommaString(*vc.tags) } if vc.memory != nil { vcMemoryBytes := *vc.memory * 1024 * 1024 // MB to B diff --git a/local/main.tf b/local/main.tf index 14209ce..b84ebd2 100644 --- a/local/main.tf +++ b/local/main.tf @@ -10,38 +10,26 @@ terraform { } locals { - vm_name = "testtf-remove-running" - src_vm_name = "testtf-src-empty" + vm_name = "testtf-ana-tags-2" + src_vm_name = "ana" } provider "hypercore" {} -data "hypercore_vms" "no-such-vm" { - name = "no-such-vm" +data "hypercore_vms" "src_empty" { + name = local.src_vm_name } -# resource "hypercore_vm" "vm_on" { -# group = "testtf" -# name = local.vm_name -# description = "VM created from scratch" -# vcpu = 1 -# memory = 1234 # MiB - -# # clone = { -# # source_vm_uuid = data.hypercore_vm.src_empty.vms.0.uuid -# # meta_data = "" -# # user_data = "" -# # } -# } - -# resource "hypercore_vm_power_state" "vm_on" { -# vm_uuid = hypercore_vm.vm_on.id -# state = "SHUTOFF" // RUNNING SHUTOFF -# } - -# output "vm_on_uuid" { -# value = hypercore_vm.vm_on.id -# } -# output "power_state" { -# value = hypercore_vm_power_state.vm_on.state -# } +resource "hypercore_vm" "vm_on" { + tags = ["ana-tftag2"] + name = local.vm_name + description = "VM created from scratch" + vcpu = 1 + memory = 1234 # MiB + + clone = { + source_vm_uuid = data.hypercore_vms.src_empty.vms.0.uuid + meta_data = "" + user_data = "" + } +}