From 35eb9290dbd22b314ab2738539eb5bbfe3ba5f77 Mon Sep 17 00:00:00 2001 From: Ujjwal Kumar Date: Thu, 5 Dec 2024 20:03:53 +0530 Subject: [PATCH] Added patch operations for cluster network attachments --- ibm/service/vpc/resource_ibm_is_instance.go | 260 +++++++++++++++++++- 1 file changed, 256 insertions(+), 4 deletions(-) diff --git a/ibm/service/vpc/resource_ibm_is_instance.go b/ibm/service/vpc/resource_ibm_is_instance.go index 5c93f34e9f..73ea36f316 100644 --- a/ibm/service/vpc/resource_ibm_is_instance.go +++ b/ibm/service/vpc/resource_ibm_is_instance.go @@ -218,10 +218,11 @@ func ResourceIBMISInstance() *schema.Resource { // cluster changes "cluster_network_attachments": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - Description: "The cluster network attachments for this virtual server instance.The cluster network attachments are ordered for consistent instance configuration.", + Type: schema.TypeList, + Optional: true, + Computed: true, + DiffSuppressFunc: diffSuppressClusterNetworkAttachment, + Description: "The cluster network attachments for this virtual server instance.The cluster network attachments are ordered for consistent instance configuration.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "cluster_network_interface": &schema.Schema{ @@ -8151,3 +8152,254 @@ func setVolumePrototypesInState(d *schema.ResourceData, instance *vpcv1.Instance return finalList, nil } + +// diffSuppressClusterNetworkAttachment handles comparing old and new cluster network attachments +// to determine if there are actual changes that require an update +func diffSuppressClusterNetworkAttachment(k, old, new string, d *schema.ResourceData) bool { + // If values are equal, no changes needed + if old == new { + return true + } + + // Get the lists of old and new attachments + oldAttachments, newAttachments := []interface{}{}, []interface{}{} + if v, ok := d.GetOk("cluster_network_attachments"); ok { + newAttachments = v.([]interface{}) + } + if v, ok := d.GetOk("cluster_network_attachments"); ok { + oldAttachments = v.([]interface{}) + } + + // If lengths differ, there are definitely changes + if len(oldAttachments) != len(newAttachments) { + return false + } + + // Compare each attachment + for i := range oldAttachments { + oldAttach := oldAttachments[i].(map[string]interface{}) + newAttach := newAttachments[i].(map[string]interface{}) + + // Compare cluster_network_interface + oldInterface := oldAttach["cluster_network_interface"].([]interface{})[0].(map[string]interface{}) + newInterface := newAttach["cluster_network_interface"].([]interface{})[0].(map[string]interface{}) + + // Compare key properties + if !compareInterfaces(oldInterface, newInterface) { + return false + } + } + + return true +} + +// compareInterfaces compares the key properties of cluster network interfaces +func compareInterfaces(old, new map[string]interface{}) bool { + // Compare auto_delete + if old["auto_delete"] != new["auto_delete"] { + return false + } + + // Compare name + if old["name"] != new["name"] { + return false + } + + // Compare primary_ip + oldPrimaryIP := old["primary_ip"].([]interface{}) + newPrimaryIP := new["primary_ip"].([]interface{}) + if len(oldPrimaryIP) != len(newPrimaryIP) { + return false + } + if len(oldPrimaryIP) > 0 { + if !comparePrimaryIP(oldPrimaryIP[0].(map[string]interface{}), newPrimaryIP[0].(map[string]interface{})) { + return false + } + } + + // Compare subnet + oldSubnet := old["subnet"].([]interface{}) + newSubnet := new["subnet"].([]interface{}) + if len(oldSubnet) != len(newSubnet) { + return false + } + if len(oldSubnet) > 0 { + if !compareSubnet(oldSubnet[0].(map[string]interface{}), newSubnet[0].(map[string]interface{})) { + return false + } + } + + return true +} + +// comparePrimaryIP compares primary IP configurations +func comparePrimaryIP(old, new map[string]interface{}) bool { + return old["id"] == new["id"] && + old["address"] == new["address"] && + old["auto_delete"] == new["auto_delete"] && + old["name"] == new["name"] +} + +// compareSubnet compares subnet configurations +func compareSubnet(old, new map[string]interface{}) bool { + return old["id"] == new["id"] +} +func handleClusterNetworkAttachmentUpdate(d *schema.ResourceData, instanceC *vpcv1.VpcV1) error { + if d.HasChange("cluster_network_attachments") && !d.IsNewResource() { + old, new := d.GetChange("cluster_network_attachments") + oldAttachments := old.([]interface{}) + newAttachments := new.([]interface{}) + + // Track attachments to remove, add, and update + toRemove := []string{} + toAdd := []map[string]interface{}{} + toUpdate := []map[string]interface{}{} + + // Build map of old attachments by name for easier lookup + oldAttachMap := make(map[string]map[string]interface{}) + for _, attachment := range oldAttachments { + attach := attachment.(map[string]interface{}) + oldAttachMap[attach["name"].(string)] = attach + } + + // Analyze new attachments + for _, attachment := range newAttachments { + attach := attachment.(map[string]interface{}) + name := attach["name"].(string) + + if oldAttach, exists := oldAttachMap[name]; exists { + // Check if update needed + if !compareInterfaces( + oldAttach["cluster_network_interface"].([]interface{})[0].(map[string]interface{}), + attach["cluster_network_interface"].([]interface{})[0].(map[string]interface{}), + ) { + toUpdate = append(toUpdate, attach) + } + delete(oldAttachMap, name) + } else { + toAdd = append(toAdd, attach) + } + } + + // Remaining old attachments need to be removed + for _, attach := range oldAttachMap { + if id, ok := attach["id"].(string); ok { + toRemove = append(toRemove, id) + } + } + + // Process removals + instanceID := d.Id() + for _, id := range toRemove { + deleteOptions := &vpcv1.DeleteInstanceClusterNetworkAttachmentOptions{ + InstanceID: &instanceID, + ID: &id, + } + _, _, err := instanceC.DeleteInstanceClusterNetworkAttachment(deleteOptions) + if err != nil { + return fmt.Errorf("error removing cluster network attachment: %v", err) + } + } + + // Process additions + for _, attach := range toAdd { + createOptions := buildCreateClusterNetworkAttachmentOptions(d.Id(), attach) + _, _, err := instanceC.CreateClusterNetworkAttachment(createOptions) + if err != nil { + return fmt.Errorf("error adding cluster network attachment: %v", err) + } + } + + // Process updates + for _, attach := range toUpdate { + updateOptions := buildUpdateClusterNetworkAttachmentOptions(d.Id(), attach) + _, _, err := instanceC.UpdateInstanceClusterNetworkAttachment(updateOptions) + if err != nil { + return fmt.Errorf("error updating cluster network attachment: %v", err) + } + } + } + return nil +} + +func buildCreateClusterNetworkAttachmentOptions(instanceID string, attachment map[string]interface{}) *vpcv1.CreateClusterNetworkAttachmentOptions { + // Extract network interface data + networkInterface := attachment["cluster_network_interface"].([]interface{})[0].(map[string]interface{}) + + // Build cluster network interface + clusterNetworkInterface := &vpcv1.InstanceClusterNetworkAttachmentPrototypeClusterNetworkInterface{} + + if autoDelete, ok := networkInterface["auto_delete"].(bool); ok { + clusterNetworkInterface.AutoDelete = &autoDelete + } + + if name, ok := networkInterface["name"].(string); ok { + clusterNetworkInterface.Name = &name + } + + // Handle primary IP if present + if primaryIPList, ok := networkInterface["primary_ip"].([]interface{}); ok && len(primaryIPList) > 0 { + primaryIP := primaryIPList[0].(map[string]interface{}) + primaryIPPrototype := &vpcv1.ClusterNetworkInterfacePrimaryIPPrototype{} + + if address, ok := primaryIP["address"].(string); ok { + primaryIPPrototype.Address = &address + } + if autoDelete, ok := primaryIP["auto_delete"].(bool); ok { + primaryIPPrototype.AutoDelete = &autoDelete + } + if name, ok := primaryIP["name"].(string); ok { + primaryIPPrototype.Name = &name + } + + clusterNetworkInterface.PrimaryIP = primaryIPPrototype + } + + // Handle subnet if present + if subnetList, ok := networkInterface["subnet"].([]interface{}); ok && len(subnetList) > 0 { + subnet := subnetList[0].(map[string]interface{}) + if id, ok := subnet["id"].(string); ok { + clusterNetworkInterface.Subnet = &vpcv1.ClusterNetworkSubnetIdentity{ + ID: &id, + } + } + } + + // Get attachment name + attachmentName := attachment["name"].(string) + + // Create the options struct + createOptions := &vpcv1.CreateClusterNetworkAttachmentOptions{ + InstanceID: &instanceID, + Name: &attachmentName, + ClusterNetworkInterface: clusterNetworkInterface, + } + + return createOptions +} + +func buildUpdateClusterNetworkAttachmentOptions(instanceID string, attachment map[string]interface{}) *vpcv1.UpdateInstanceClusterNetworkAttachmentOptions { + // Extract network interface data + networkInterface := attachment["cluster_network_interface"].([]interface{})[0].(map[string]interface{}) + + // Build cluster network interface patch + + clusterNetworkInterface := &vpcv1.InstanceClusterNetworkAttachmentPatch{} + + if name, ok := networkInterface["name"].(string); ok { + clusterNetworkInterface.Name = &name + } + clusterNetworkInterfaceAsPatch, _ := clusterNetworkInterface.AsPatch() + + // Get attachment ID and name + attachmentID := attachment["id"].(string) + + // Create the options struct + updateOptions := &vpcv1.UpdateInstanceClusterNetworkAttachmentOptions{ + InstanceID: &instanceID, + ID: &attachmentID, + InstanceClusterNetworkAttachmentPatch: clusterNetworkInterfaceAsPatch, + } + + return updateOptions +}