diff --git a/CHANGELOG.md b/CHANGELOG.md index f86fc89ab..22aa29b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # +## 2.9.2 (nNot Released) + +BUG FIX: +* `resource/vsphere_compute_cluster_vm_group`: Updates resource to allow for additional virtual + machines to be adding or removed from a VM Group. Must be ran in conjunction with and import. + ([#2260]https://github.com/hashicorp/terraform-provider-vsphere/pull/2260) + ## 2.9.1 (September 9, 2024) BUG FIX: diff --git a/vsphere/resource_vsphere_compute_cluster_vm_group.go b/vsphere/resource_vsphere_compute_cluster_vm_group.go index 2bc006cb3..fb080ed15 100644 --- a/vsphere/resource_vsphere_compute_cluster_vm_group.go +++ b/vsphere/resource_vsphere_compute_cluster_vm_group.go @@ -4,6 +4,7 @@ package vsphere import ( + "context" "encoding/json" "errors" "fmt" @@ -63,6 +64,17 @@ func resourceVSphereComputeClusterVMGroupCreate(d *schema.ResourceData, meta int return err } + // Check if the VM group already exists + exists, err := resourceVSphereComputeClusterVMGroupFindEntry(cluster, name) + if err != nil { + return err + } + + if exists != nil { + log.Printf("[DEBUG] %s: VM group already exists, calling update", exists.Name) + return resourceVSphereComputeClusterVMGroupUpdate(d, meta) + } + info, err := expandClusterVMGroup(d, meta, name) if err != nil { return err @@ -140,17 +152,82 @@ func resourceVSphereComputeClusterVMGroupUpdate(d *schema.ResourceData, meta int return err } - info, err := expandClusterVMGroup(d, meta, name) + // Retrieve the existing VM group information. + existingGroup, err := getCurrentVMsInGroup(cluster, name) + if err != nil { + return err + } + + // Check if existingGroup is nil. + if existingGroup == nil { + return fmt.Errorf("VM group %s not found", name) + } + + // Expand the new VM group information. + newInfo, err := expandClusterVMGroup(d, meta, name) if err != nil { return err } + + // Convert existing and new virtual machines to string slices for diffVmGroup. + existingVMs := make([]string, len(existingGroup.Vm)) + for i, vm := range existingGroup.Vm { + existingVMs[i] = vm.Value + } + + newVMs := make([]string, len(newInfo.Vm)) + for i, vm := range newInfo.Vm { + newVMs[i] = vm.Value + } + + // Use diffVmGroup to find added and removed virtual machines from virtual machine group. + addedVMs, removedVMs := diffVmGroup(existingVMs, newVMs) + + // Convert addedVMs and removedVMs back to ManagedObjectReference slices. + addedVMRefs := make([]types.ManagedObjectReference, len(addedVMs)) + for i, vm := range addedVMs { + addedVMRefs[i] = types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: vm, + } + } + + removedVMRefs := make([]types.ManagedObjectReference, len(removedVMs)) + for i, vm := range removedVMs { + removedVMRefs[i] = types.ManagedObjectReference{ + Type: "VirtualMachine", + Value: vm, + } + } + + // Merge existing virtual machines with added virtual machines and remove duplicates. + mergedVMs := append(existingGroup.Vm, addedVMRefs...) + vmMap := make(map[types.ManagedObjectReference]bool) + for _, vm := range mergedVMs { + vmMap[vm] = true + } + for _, vm := range removedVMRefs { + delete(vmMap, vm) + } + uniqueVMs := make([]types.ManagedObjectReference, 0, len(vmMap)) + for vm := range vmMap { + uniqueVMs = append(uniqueVMs, vm) + } + + if len(uniqueVMs) == 0 { + return fmt.Errorf("the resultant set of virtual machines in the vm group cannot be empty") + } + + // Update the VM group information with the merged list. + newInfo.Vm = uniqueVMs + spec := &types.ClusterConfigSpecEx{ GroupSpec: []types.ClusterGroupSpec{ { ArrayUpdateSpec: types.ArrayUpdateSpec{ Operation: types.ArrayUpdateOperationEdit, }, - Info: info, + Info: newInfo, }, }, } @@ -397,3 +474,41 @@ func resourceVSphereComputeClusterVMGroupClient(meta interface{}) (*govmomi.Clie } return client, nil } + +func diffVmGroup(oldVMs, newVMs []string) ([]string, []string) { + oldVMMap := make(map[string]bool) + for _, vm := range oldVMs { + oldVMMap[vm] = true + } + + var addedVMs, removedVMs []string + for _, vm := range newVMs { + if !oldVMMap[vm] { + addedVMs = append(addedVMs, vm) + } + delete(oldVMMap, vm) + } + + for vm := range oldVMMap { + removedVMs = append(removedVMs, vm) + } + + return addedVMs, removedVMs +} + +// getCurrentVMsInGroup retrieves the current VMs in the specified VM group from the vSphere cluster. +func getCurrentVMsInGroup(cluster *object.ClusterComputeResource, groupName string) (*types.ClusterVmGroup, error) { + ctx := context.TODO() + groups, err := cluster.Configuration(ctx) + if err != nil { + return nil, err + } + + for _, group := range groups.Group { + if vmGroup, ok := group.(*types.ClusterVmGroup); ok && vmGroup.Name == groupName { + return vmGroup, nil + } + } + + return nil, fmt.Errorf("VM group %s not found", groupName) +} diff --git a/website/docs/r/compute_cluster_vm_group.html.markdown b/website/docs/r/compute_cluster_vm_group.html.markdown index cbf0f4da7..a1cc76191 100644 --- a/website/docs/r/compute_cluster_vm_group.html.markdown +++ b/website/docs/r/compute_cluster_vm_group.html.markdown @@ -108,6 +108,11 @@ resource. Make sure your names are unique across both resources. [tf-vsphere-cluster-host-group-resource]: /docs/providers/vsphere/r/compute_cluster_host_group.html +~> **NOTE:** To update a existing VM group, you must first import the group with `import` command in +[Importing](#importing) section. When importing a VM group, validate that all virtual machines that +need to be in the group are included in the `virtual_machine_ids`; otherwise, any virtual machines +that are not in `virtual_machine_ids` the included will be removed from the group. + ## Attribute Reference The only attribute this resource exports is the `id` of the resource, which is