From 98695a5bd6be6095d59063f8deb916c8f8c13eca Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Fri, 28 Aug 2026 15:28:02 +0530 Subject: [PATCH] Fix cloudstack_ipaddress delete silently succeeding on source-NAT IPs resourceCloudStackIPAddressDelete skipped the disassociate call entirely when is_source_nat was true and returned nil, so Terraform reported the resource destroyed while CloudStack left the IP allocated. Always call DisassociateIpAddress and propagate whatever CloudStack returns instead of swallowing it, matching every other error path in this file. Verified against a live 4.23.0.0 lab: against an implemented network with a running virtual router, CloudStack now correctly rejects the destroy (error 530, IP still in use for source NAT) instead of Terraform falsely reporting success; against a network that was never implemented, the disassociate genuinely succeeds and is now reported accurately. --- cloudstack/resource_cloudstack_ipaddress.go | 28 ++++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/cloudstack/resource_cloudstack_ipaddress.go b/cloudstack/resource_cloudstack_ipaddress.go index 1af251f2..a0c4ea2c 100644 --- a/cloudstack/resource_cloudstack_ipaddress.go +++ b/cloudstack/resource_cloudstack_ipaddress.go @@ -276,23 +276,21 @@ func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) e } func resourceCloudStackIPAddressDelete(d *schema.ResourceData, meta interface{}) error { - if !d.Get("is_source_nat").(bool) { - cs := meta.(*cloudstack.CloudStackClient) - - // Create a new parameter struct - p := cs.Address.NewDisassociateIpAddressParams(d.Id()) - - // Disassociate the IP address - if _, err := cs.Address.DisassociateIpAddress(p); err != nil { - // This is a very poor way to be told the ID does no longer exist :( - if strings.Contains(err.Error(), fmt.Sprintf( - "Invalid parameter id value=%s due to incorrect long value format, "+ - "or entity does not exist", d.Id())) { - return nil - } + cs := meta.(*cloudstack.CloudStackClient) - return fmt.Errorf("Error disassociating IP address %s: %s", d.Id(), err) + // Create a new parameter struct + p := cs.Address.NewDisassociateIpAddressParams(d.Id()) + + // Disassociate the IP address + if _, err := cs.Address.DisassociateIpAddress(p); err != nil { + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil } + + return fmt.Errorf("Error disassociating IP address %s: %s", d.Id(), err) } return nil