This is an automated email from the ASF dual-hosted git repository.
sureshanaparti pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
The following commit(s) were added to refs/heads/main by this push:
new 969683c Fix cloudstack_ipaddress delete silently succeeding on
source-NAT IPs (#331)
969683c is described below
commit 969683cf5d4a0dc52a8790aceb2fa91d32f6e54f
Author: Manoj Kumar <[email protected]>
AuthorDate: Thu Sep 3 19:56:27 2026 +0530
Fix cloudstack_ipaddress delete silently succeeding on source-NAT IPs (#331)
* 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.
* Treat source-NAT disassociate failure as a no-op on delete
CloudStack rejects DisassociateIpAddress for a source NAT IP while its
owning network/VPC still exists, so the now-unconditional disassociate
call added in the previous commit fails terraform destroy on any
config that manages a source NAT IP as its own cloudstack_ipaddress
resource (e.g. imported directly). Deleting the network/VPC releases
the IP as a side effect, so treat that specific CloudStack error as
success here instead of failing the destroy.
---
cloudstack/resource_cloudstack_ipaddress.go | 34 ++++++++++++++++-------------
website/docs/r/ipaddress.html.markdown | 6 +++++
2 files changed, 25 insertions(+), 15 deletions(-)
diff --git a/cloudstack/resource_cloudstack_ipaddress.go
b/cloudstack/resource_cloudstack_ipaddress.go
index 1af251f..b1c0b81 100644
--- a/cloudstack/resource_cloudstack_ipaddress.go
+++ b/cloudstack/resource_cloudstack_ipaddress.go
@@ -276,23 +276,27 @@ 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)
+
+ // 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)
+ // A source NAT IP can't be disassociated while its network/VPC
still exists;
+ // deleting that network/VPC releases it instead, so treat this
as a no-op.
+ if strings.Contains(err.Error(), "used for source nat purposes
and can not be disassociated") {
+ return nil
}
+
+ return fmt.Errorf("Error disassociating IP address %s: %s",
d.Id(), err)
}
return nil
diff --git a/website/docs/r/ipaddress.html.markdown
b/website/docs/r/ipaddress.html.markdown
index 19c1898..28552d5 100644
--- a/website/docs/r/ipaddress.html.markdown
+++ b/website/docs/r/ipaddress.html.markdown
@@ -120,3 +120,9 @@ When importing into a project you need to prefix the import
ID with the project
```shell
$ terraform import cloudstack_ipaddress.default
my-project/6226ea4d-9cbe-4cc9-b30c-b9532146da5b
```
+
+*NOTE: A source NAT IP cannot be released on its own while its network or VPC
still
+exists; CloudStack ties its lifecycle to the owning network/VPC and releases it
+automatically when that network/VPC is deleted. Destroying a
`cloudstack_ipaddress`
+resource that manages a source NAT IP is a no-op until the owning network/VPC
is
+also destroyed.*