Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions cloudstack/resource_cloudstack_ipaddress.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +286 to 291

@sudo87 sudo87 Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, that special-case (mapping CloudStack's "entity does not exist" error to nil) is intentional, for idempotent deletes when the IP is already gone. Updated the PR description to call that out explicitly rather than implying every error is now propagated.


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
Expand Down
6 changes: 6 additions & 0 deletions website/docs/r/ipaddress.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Loading