Repository: ovh/terraform-provider-ovh

Issue:
[BUG] limit set in ovh_cloud_project_storage keep sending 1000 objects
### Describe the bug
the limit parameter in ovh_cloud_project_storage don't work, it keeps returning 1000 objects
i have to migrate more than 100 buckets, so my tfstate will become huge

### Terraform Version

1.12.2

### OVH Terraform Provider Version

2.1.0

### Affected Resource(s)

- ovh_cloud_project_storage


### Expected Behavior

when limit is set to 5 it should return only 5 objects


### Actual Behavior

it keeps returning 1000 objects

### Steps to Reproduce

Please list the steps required to reproduce the issue, for example:
Create a bucket with terraform

resource "ovh_cloud_project_storage" "buckets" {
  service_name = var.ovh_project_id
  region_name  = var.ovh_main_region
  name         = "testbucket"
  limit = 5
  versioning = {
    status = "enabled"
  }
  encryption = {
    sse_algorithm = "AES256"
  }

}

terraform apply

then add more than 5 objects into the bucket

then do slight changes to the tf file to generate a new tfstate

see in tfstate the number of objects returned .


--




Discussion:
@amstuta: Hello @drpark, thanks for opening this issue!

Indeed, there is a bug in the provider and the parameters used to filter the returned objects are not used. I'll open a PR with a fix shortly.

Solution patch:
diff --git a/ovh/resource_cloud_project_storage.go b/ovh/resource_cloud_project_storage.go
index 3aca06e7..df1a8072 100644
--- a/ovh/resource_cloud_project_storage.go
+++ b/ovh/resource_cloud_project_storage.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net/url"
+	"strconv"
 	"strings"
 
 	"github.com/hashicorp/terraform-plugin-framework/path"
@@ -91,7 +92,19 @@ func (r *cloudProjectStorageResource) Read(ctx context.Context, req resource.Rea
 		return
 	}
 
-	endpoint := "/cloud/project/" + url.PathEscape(data.ServiceName.ValueString()) + "/region/" + url.PathEscape(data.RegionName.ValueString()) + "/storage/" + url.PathEscape(data.Name.ValueString())
+	// Add filters if defined
+	queryParams := url.Values{}
+	if !data.Limit.IsNull() && !data.Limit.IsUnknown() {
+		queryParams.Add("limit", strconv.FormatInt(data.Limit.ValueInt64(), 10))
+	}
+	if !data.Marker.IsNull() && !data.Marker.IsUnknown() {
+		queryParams.Add("marker", data.Marker.ValueString())
+	}
+	if !data.Prefix.IsNull() && !data.Prefix.IsUnknown() {
+		queryParams.Add("prefix", data.Prefix.ValueString())
+	}
+
+	endpoint := "/cloud/project/" + url.PathEscape(data.ServiceName.ValueString()) + "/region/" + url.PathEscape(data.RegionName.ValueString()) + "/storage/" + url.PathEscape(data.Name.ValueString()) + "?" + queryParams.Encode()
 	if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil {
 		resp.Diagnostics.AddError(
 			fmt.Sprintf("Error calling Get %s", endpoint),
@@ -100,10 +113,10 @@ func (r *cloudProjectStorageResource) Read(ctx context.Context, req resource.Rea
 		return
 	}
 
-	data.MergeWith(&responseData)
+	responseData.MergeWith(&data)
 
 	// Save updated data into Terraform state
-	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+	resp.Diagnostics.Append(resp.State.Set(ctx, &responseData)...)
 }
 
 func (r *cloudProjectStorageResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
@@ -131,8 +144,20 @@ func (r *cloudProjectStorageResource) Update(ctx context.Context, req resource.U
 		return
 	}
 
+	// Add filters if defined
+	queryParams := url.Values{}
+	if !planData.Limit.IsNull() && !planData.Limit.IsUnknown() {
+		queryParams.Add("limit", strconv.FormatInt(planData.Limit.ValueInt64(), 10))
+	}
+	if !planData.Marker.IsNull() && !planData.Marker.IsUnknown() {
+		queryParams.Add("marker", planData.Marker.ValueString())
+	}
+	if !planData.Prefix.IsNull() && !planData.Prefix.IsUnknown() {
+		queryParams.Add("prefix", planData.Prefix.ValueString())
+	}
+
 	// Read updated resource
-	endpoint = "/cloud/project/" + url.PathEscape(data.ServiceName.ValueString()) + "/region/" + url.PathEscape(data.RegionName.ValueString()) + "/storage/" + url.PathEscape(data.Name.ValueString())
+	endpoint = "/cloud/project/" + url.PathEscape(data.ServiceName.ValueString()) + "/region/" + url.PathEscape(data.RegionName.ValueString()) + "/storage/" + url.PathEscape(data.Name.ValueString()) + "?" + queryParams.Encode()
 	if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil {
 		resp.Diagnostics.AddError(
 			fmt.Sprintf("Error calling Get %s", endpoint),
diff --git a/ovh/resource_cloud_project_region_storage_gen.go b/ovh/resource_cloud_project_storage_gen.go
similarity index 99%
rename from ovh/resource_cloud_project_region_storage_gen.go
rename to ovh/resource_cloud_project_storage_gen.go
index cfd72ea8..6649d396 100644
--- a/ovh/resource_cloud_project_region_storage_gen.go
+++ b/ovh/resource_cloud_project_storage_gen.go
@@ -408,29 +408,6 @@ func (v *CloudProjectRegionStorageModel) MergeWith(other *CloudProjectRegionStor
 		v.Name = other.Name
 	}
 
-	if (v.Objects.IsUnknown() || v.Objects.IsNull()) && !other.Objects.IsUnknown() {
-		v.Objects = other.Objects
-	} else if !other.Objects.IsUnknown() {
-		newSlice := make([]attr.Value, 0)
-		elems := v.Objects.Elements()
-		newElems := other.Objects.Elements()
-
-		if len(elems) != len(newElems) {
-			v.Objects = other.Objects
-		} else {
-			for idx, e := range elems {
-				tmp := e.(ObjectsValue)
-				tmp2 := newElems[idx].(ObjectsValue)
-				tmp.MergeWith(&tmp2)
-				newSlice = append(newSlice, tmp)
-			}
-
-			v.Objects = ovhtypes.TfListNestedValue[ObjectsValue]{
-				ListValue: basetypes.NewListValueMust(ObjectsValue{}.Type(context.Background()), newSlice),
-			}
-		}
-	}
-
 	if (v.ObjectsCount.IsUnknown() || v.ObjectsCount.IsNull()) && !other.ObjectsCount.IsUnknown() {
 		v.ObjectsCount = other.ObjectsCount
 	}
@@ -1944,10 +1921,9 @@ func (v *ReplicationValue) UnmarshalJSON(data []byte) error {
 }
 
 func (v *ReplicationValue) MergeWith(other *ReplicationValue) {
-
 	if (v.Rules.IsUnknown() || v.Rules.IsNull()) && !other.Rules.IsUnknown() {
 		v.Rules = other.Rules
-	} else if !other.Rules.IsUnknown() {
+	} else if !other.Rules.IsUnknown() && !other.Rules.IsNull() {
 		newSlice := make([]attr.Value, 0)
 		elems := v.Rules.Elements()
 		newElems := other.Rules.Elements()



Test patch:
diff --git a/ovh/resource_cloud_project_storage_test.go b/ovh/resource_cloud_project_storage_test.go
index 04c67f07..914734f7 100644
--- a/ovh/resource_cloud_project_storage_test.go
+++ b/ovh/resource_cloud_project_storage_test.go
@@ -51,45 +51,43 @@ func TestAccCloudProjectRegionStorage_basic(t *testing.T) {
 func TestAccCloudProjectRegionStorage_withReplication(t *testing.T) {
 	bucketName := acctest.RandomWithPrefix(test_prefix)
 	replicaBucketName := acctest.RandomWithPrefix(test_prefix)
-	config := fmt.Sprintf(`
-	resource "ovh_cloud_project_storage" "storage" {
-		service_name = "%s"
-		region_name = "GRA"
-		name = "%s"
-
-		versioning = {
-			status = "enabled"
-		}
-
-		replication = {
-			rules = [
-				{
-					id          = "test"
-					priority    = 1
-					status      = "enabled"
-					destination = {
-						name   = "%s"
-						region = "GRA"
-					}
-					filter = {
-						"prefix" = "test"
-						"tags"   = {
-							"key": "test"
-						}
-					}
-					delete_marker_replication = "disabled"
-				}
-			]
-		}
-	}
-	`, os.Getenv("OVH_CLOUD_PROJECT_SERVICE_TEST"), bucketName, replicaBucketName)
 
 	resource.Test(t, resource.TestCase{
 		PreCheck:                 func() { testAccPreCheckCloud(t); testAccCheckCloudProjectExists(t) },
 		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
 		Steps: []resource.TestStep{
 			{
-				Config: config,
+				Config: fmt.Sprintf(`
+					resource "ovh_cloud_project_storage" "storage" {
+						service_name = "%s"
+						region_name = "GRA"
+						name = "%s"
+
+						versioning = {
+							status = "enabled"
+						}
+
+						replication = {
+							rules = [
+								{
+									id          = "test"
+									priority    = 1
+									status      = "enabled"
+									destination = {
+										name   = "%s"
+										region = "GRA"
+									}
+									filter = {
+										"prefix" = "test"
+										"tags"   = {
+											"key": "test"
+										}
+									}
+									delete_marker_replication = "disabled"
+								}
+							]
+						}
+					}`, os.Getenv("OVH_CLOUD_PROJECT_SERVICE_TEST"), bucketName, replicaBucketName),
 				Check: resource.ComposeTestCheckFunc(
 					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "name", bucketName),
 					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "region", "GRA"),
@@ -105,6 +103,53 @@ func TestAccCloudProjectRegionStorage_withReplication(t *testing.T) {
 					resource.TestCheckResourceAttrSet("ovh_cloud_project_storage.storage", "virtual_host"),
 				),
 			},
+			{
+				Config: fmt.Sprintf(`
+					resource "ovh_cloud_project_storage" "storage" {
+						service_name = "%s"
+						region_name = "GRA"
+						name = "%s"
+
+						versioning = {
+							status = "enabled"
+						}
+
+						replication = {
+							rules = [
+								{
+									id          = "test"
+									priority    = 1
+									status      = "enabled"
+									destination = {
+										name   = "%s"
+										region = "GRA"
+									}
+									filter = {
+										"prefix" = "test-updated"
+										"tags"   = {
+											"key": "test-updated"
+										}
+									}
+									delete_marker_replication = "disabled"
+								}
+							]
+						}
+					} `, os.Getenv("OVH_CLOUD_PROJECT_SERVICE_TEST"), bucketName, replicaBucketName),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "name", bucketName),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "region", "GRA"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "versioning.status", "enabled"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "encryption.sse_algorithm", "plaintext"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.#", "1"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.0.id", "test"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.0.priority", "1"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.0.status", "enabled"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.0.destination.name", replicaBucketName),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.0.destination.region", "GRA"),
+					resource.TestCheckResourceAttr("ovh_cloud_project_storage.storage", "replication.rules.0.filter.prefix", "test-updated"),
+					resource.TestCheckResourceAttrSet("ovh_cloud_project_storage.storage", "virtual_host"),
+				),
+			},
 			{
 				ImportState:                          true,
 				ImportStateVerify:                    true,