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 f52481e Fix test findings: storage IP range updates, service offering
import, project display_text (#332)
f52481e is described below
commit f52481ef24aff852e090674941c485a28d1c15d6
Author: Manoj Kumar <[email protected]>
AuthorDate: Thu Sep 3 20:00:07 2026 +0530
Fix test findings: storage IP range updates, service offering import,
project display_text (#332)
* Mark storage_network_ip_range netmask/start_ip/end_ip as ForceNew
* Document ForceNew-field footgun after cloudstack_service_offering import
* Add snake_case display_text field to cloudstack_project, deprecate
displaytext
* Fix cloudstack_project display_text/displaytext import and update bugs
* Narrow storage_network_ip_range ForceNew docs to overlapping edits
* Add ConflictsWith between cloudstack_project displaytext/display_text
---
cloudstack/resource_cloudstack_project.go | 51 ++++++++++++++++++----
...resource_cloudstack_storage_network_ip_range.go | 19 ++++----
website/docs/r/project.html.markdown | 5 ++-
website/docs/r/service_offering.html.markdown | 11 +++++
.../docs/r/storage_network_ip_range.html.markdown | 19 +++++++-
5 files changed, 86 insertions(+), 19 deletions(-)
diff --git a/cloudstack/resource_cloudstack_project.go
b/cloudstack/resource_cloudstack_project.go
index 9c02846..db004af 100644
--- a/cloudstack/resource_cloudstack_project.go
+++ b/cloudstack/resource_cloudstack_project.go
@@ -47,8 +47,18 @@ func resourceCloudStackProject() *schema.Resource {
},
"displaytext": {
- Type: schema.TypeString,
- Optional: true,
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ Deprecated: "use display_text instead",
+ ConflictsWith: []string{"display_text"},
+ },
+
+ "display_text": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ ConflictsWith: []string{"displaytext"},
},
"domain": {
@@ -76,12 +86,32 @@ func resourceCloudStackProject() *schema.Resource {
}
}
+// projectDisplayText resolves the effective display text from the new
+// display_text field and the deprecated displaytext field. ConflictsWith
+// keeps config from setting both at once, but both fields are Computed, so
+// GetOk alone can't tell a real edit from a stale Computed value left over
+// from the last refresh - HasChange is checked first to find the one
+// actually edited. The final GetOk/displaytext fallback only matters when
+// neither changed (e.g. a fresh create, or a no-op update).
+func projectDisplayText(d *schema.ResourceData) string {
+ if d.HasChange("display_text") {
+ return d.Get("display_text").(string)
+ }
+ if d.HasChange("displaytext") {
+ return d.Get("displaytext").(string)
+ }
+ if v, ok := d.GetOk("display_text"); ok {
+ return v.(string)
+ }
+ return d.Get("displaytext").(string)
+}
+
func resourceCloudStackProjectCreate(d *schema.ResourceData, meta any) error {
cs := meta.(*cloudstack.CloudStackClient)
// Get the name and displaytext
name := d.Get("name").(string)
- displaytext := d.Get("displaytext").(string)
+ displaytext := projectDisplayText(d)
// Get domain if provided
var domain string
@@ -101,7 +131,6 @@ func resourceCloudStackProjectCreate(d
*schema.ResourceData, meta any) error {
// Set the basic attributes to match the existing
project
d.Set("name", existingProject.Name)
- d.Set("displaytext", existingProject.Displaytext)
d.Set("domain", existingProject.Domain)
return resourceCloudStackProjectRead(d, meta)
@@ -327,9 +356,13 @@ func resourceCloudStackProjectRead(d *schema.ResourceData,
meta any) error {
// Set the basic attributes
d.Set("name", project.Name)
- d.Set("displaytext", project.Displaytext)
d.Set("domain", project.Domain)
+ // Both fields are Computed, so setting both unconditionally reflects
the
+ // API value without creating a diff for a config that only sets one.
+ d.Set("displaytext", project.Displaytext)
+ d.Set("display_text", project.Displaytext)
+
// Handle owner information more safely
// Only set the account, accountid, and userid if they were explicitly
set in the configuration
// and if the owner information is available
@@ -397,7 +430,7 @@ func resourceCloudStackProjectUpdate(d
*schema.ResourceData, meta any) error {
cs := meta.(*cloudstack.CloudStackClient)
// Check if the name or displaytext is changed
- if d.HasChange("name") || d.HasChange("displaytext") {
+ if d.HasChange("name") || d.HasChange("displaytext") ||
d.HasChange("display_text") {
// Create a new parameter struct
p := cs.Project.NewUpdateProjectParams(d.Id())
@@ -409,8 +442,8 @@ func resourceCloudStackProjectUpdate(d
*schema.ResourceData, meta any) error {
p.SetName(d.Get("name").(string))
}
- if d.HasChange("displaytext") {
- p.SetDisplaytext(d.Get("displaytext").(string))
+ if d.HasChange("displaytext") || d.HasChange("display_text") {
+ p.SetDisplaytext(projectDisplayText(d))
}
log.Printf("[DEBUG] Updating project %s", d.Id())
@@ -490,7 +523,7 @@ func resourceCloudStackProjectUpdate(d
*schema.ResourceData, meta any) error {
return retry.RetryableError(fmt.Errorf("project name
not updated yet"))
}
- if d.HasChange("displaytext") && project.Displaytext !=
d.Get("displaytext").(string) {
+ if (d.HasChange("displaytext") || d.HasChange("display_text"))
&& project.Displaytext != projectDisplayText(d) {
log.Printf("[DEBUG] Project %s displaytext not updated
yet, retrying...", d.Id())
return retry.RetryableError(fmt.Errorf("project
displaytext not updated yet"))
}
diff --git a/cloudstack/resource_cloudstack_storage_network_ip_range.go
b/cloudstack/resource_cloudstack_storage_network_ip_range.go
index 709108d..24d317e 100644
--- a/cloudstack/resource_cloudstack_storage_network_ip_range.go
+++ b/cloudstack/resource_cloudstack_storage_network_ip_range.go
@@ -47,6 +47,7 @@ func resourceCloudStackStorageNetworkIpRange()
*schema.Resource {
Description: "the netmask for the storage
network IP range",
Type: schema.TypeString,
Required: true,
+ ForceNew: true,
},
"pod_id": {
Description: "the Pod ID for the storage
network IP range",
@@ -58,12 +59,14 @@ func resourceCloudStackStorageNetworkIpRange()
*schema.Resource {
Description: "the beginning IP address in the
storage network IP range",
Type: schema.TypeString,
Required: true,
+ ForceNew: true,
},
"end_ip": {
Description: "the ending IP address in the
storage network IP range",
Type: schema.TypeString,
Optional: true,
Computed: true,
+ ForceNew: true,
},
"vlan": {
Description: "the optional VLAN of the storage
network IP range",
@@ -136,17 +139,17 @@ func resourceCloudStackStorageNetworkIpRangeUpdate(d
*schema.ResourceData, meta
p := cs.Network.NewUpdateStorageNetworkIpRangeParams(d.Id())
- if v, ok := d.GetOk("netmask"); ok {
- p.SetNetmask(v.(string))
+ if d.HasChange("netmask") {
+ p.SetNetmask(d.Get("netmask").(string))
}
- if v, ok := d.GetOk("start_ip"); ok {
- p.SetStartip(v.(string))
+ if d.HasChange("start_ip") {
+ p.SetStartip(d.Get("start_ip").(string))
}
- if v, ok := d.GetOk("end_ip"); ok {
- p.SetEndip(v.(string))
+ if d.HasChange("end_ip") {
+ p.SetEndip(d.Get("end_ip").(string))
}
- if v, ok := d.GetOk("vlan"); ok {
- p.SetVlan(v.(int))
+ if d.HasChange("vlan") {
+ p.SetVlan(d.Get("vlan").(int))
}
_, err := cs.Network.UpdateStorageNetworkIpRange(p)
diff --git a/website/docs/r/project.html.markdown
b/website/docs/r/project.html.markdown
index 8e0bfe9..46a5f11 100644
--- a/website/docs/r/project.html.markdown
+++ b/website/docs/r/project.html.markdown
@@ -36,7 +36,10 @@ resource "cloudstack_project" "myproject" {
The following arguments are supported:
* `name` - (Required) The name of the project.
-* `display_text` - (Required) The display text of the project. Required for
API version 4.18 and lower compatibility. This requirement will be removed when
support for API versions older than 4.18 is dropped.
+* `display_text` - (Optional) The display text of the project.
+* `displaytext` - (Optional, **Deprecated**) Use `display_text` instead.
Retained for
+ backwards compatibility with existing state files. Conflicts with
+ `display_text` - set only one of the two in config.
* `domain` - (Optional) The domain where the project will be created. This
cannot be changed after the project is created.
* `account` - (Optional) The account who will be Admin for the project.
Requires `domain` to be set. This can be updated after the project is created.
* `accountid` - (Optional) The ID of the account owning the project. This can
be updated after the project is created.
diff --git a/website/docs/r/service_offering.html.markdown
b/website/docs/r/service_offering.html.markdown
index 6a5754f..f8feadf 100644
--- a/website/docs/r/service_offering.html.markdown
+++ b/website/docs/r/service_offering.html.markdown
@@ -109,3 +109,14 @@ $ terraform import cloudstack_service_offering.example
<SERVICEOFFERINGID>
*NOTE: The importer looks up the service offering by ID and resolves the
required `name` attribute from it, so it does not need to be set in the
configuration beforehand.*
+
+*WARNING: After import, the resource's state is refreshed with the imported
+offering's actual `cpu_number`, `cpu_speed`, `memory`, and other `ForceNew`
+attribute values. If your `.tf` configuration for the imported resource
+leaves any of those `ForceNew` attributes unset, `terraform plan` will show
+a destroy-and-recreate of the offering, since an unset attribute is treated
+as its zero value and compared against the real imported value. After
+importing, write out the full set of `ForceNew` attributes in your
+configuration (matching the values shown by `terraform state show`) before
+running `terraform plan`, or the plan may propose replacing a live, possibly
+in-use offering.*
diff --git a/website/docs/r/storage_network_ip_range.html.markdown
b/website/docs/r/storage_network_ip_range.html.markdown
index 9deb789..cc9fff3 100644
--- a/website/docs/r/storage_network_ip_range.html.markdown
+++ b/website/docs/r/storage_network_ip_range.html.markdown
@@ -33,11 +33,28 @@ The following arguments are supported:
this forces a new resource to be created.
- `gateway` - (Required) The gateway for the storage network IP range. Changing
this forces a new resource to be created.
-- `netmask` - (Required) The netmask for the storage network IP range.
+- `netmask` - (Required) The netmask for the storage network IP range. Changing
+ this forces a new resource to be created.
- `start_ip` - (Required) The beginning IP address in the storage network IP
range.
+ Changing this forces a new resource to be created.
- `end_ip` - (Optional) The ending IP address in the storage network IP range.
+ Changing this forces a new resource to be created.
- `vlan` - (Optional) The optional VLAN of the storage network IP range.
+`netmask`, `start_ip`, and `end_ip` force replacement rather than an in-place
+update: CloudStack's `updateStorageNetworkIpRange` API validates a new range
+against the record's own current IPs without excluding the record being
+updated. An edit that keeps any overlap with the current range (for example
+shrinking or extending it) fails with an IP overlap error against itself;
+only moving to a range fully disjoint from the current one would succeed via
+the API. Since Terraform can't tell which case applies before attempting the
+update, these fields always force a replacement.
+
+Replacement is more destructive than the update it replaces: the existing
+range is deleted before the new one is created (the two would overlap, so
+`create_before_destroy` can't help), so the storage network briefly has no
+IP range configured for this pod during the apply.
+
## Attributes Reference
The following attributes are exported: