You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2025/09/18 12:08:08 UTC

(cloudstack-terraform-provider) branch main updated: Add `cloudstack_limits` data source and resource (#197)

This is an automated email from the ASF dual-hosted git repository.

dahn 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 96f287a  Add `cloudstack_limits` data source and resource  (#197)
96f287a is described below

commit 96f287ac61122f05e385fb3df6a426965be18dae
Author: Ian <ia...@telus.com>
AuthorDate: Thu Sep 18 05:08:04 2025 -0700

    Add `cloudstack_limits` data source and resource  (#197)
---
 cloudstack/data_source_cloudstack_limits.go      | 210 ++++++++
 cloudstack/data_source_cloudstack_limits_test.go | 198 ++++++++
 cloudstack/provider.go                           |   2 +
 cloudstack/resource_cloudstack_account.go        |   6 +-
 cloudstack/resource_cloudstack_limits.go         | 471 ++++++++++++++++++
 cloudstack/resource_cloudstack_limits_test.go    | 579 +++++++++++++++++++++++
 website/docs/d/limits.html.markdown              |  68 +++
 website/docs/r/limits.html.markdown              |  88 ++++
 8 files changed, 1619 insertions(+), 3 deletions(-)

diff --git a/cloudstack/data_source_cloudstack_limits.go b/cloudstack/data_source_cloudstack_limits.go
new file mode 100644
index 0000000..170d7bc
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_limits.go
@@ -0,0 +1,210 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+
+	"github.com/apache/cloudstack-go/v2/cloudstack"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+func dataSourceCloudStackLimits() *schema.Resource {
+	return &schema.Resource{
+		Read: dataSourceCloudStackLimitsRead,
+		Schema: map[string]*schema.Schema{
+			"type": {
+				Type:     schema.TypeString,
+				Optional: true,
+				ValidateFunc: validation.StringInSlice([]string{
+					"instance", "ip", "volume", "snapshot", "template", "project", "network", "vpc",
+					"cpu", "memory", "primarystorage", "secondarystorage",
+				}, false), // false disables case-insensitive matching
+				Description: "The type of resource to list the limits. Available types are: " +
+					"instance, ip, volume, snapshot, template, project, network, vpc, cpu, memory, " +
+					"primarystorage, secondarystorage",
+			},
+			"account": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				Description: "List resources by account. Must be used with the domain_id parameter.",
+			},
+			"domain_id": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				Description: "List only resources belonging to the domain specified.",
+			},
+			"project": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				Description: "List resource limits by project.",
+			},
+			"limits": {
+				Type:     schema.TypeList,
+				Computed: true,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"resourcetype": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"resourcetypename": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"account": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"domain": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"domain_id": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"max": {
+							Type:     schema.TypeInt,
+							Computed: true,
+						},
+						"project": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"projectid": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func dataSourceCloudStackLimitsRead(d *schema.ResourceData, meta interface{}) error {
+	cs := meta.(*cloudstack.CloudStackClient)
+
+	// Create a new parameter struct
+	p := cs.Limit.NewListResourceLimitsParams()
+
+	// Set optional parameters
+	if v, ok := d.GetOk("type"); ok {
+		typeStr := v.(string)
+		if resourcetype, ok := resourceTypeMap[typeStr]; ok {
+			p.SetResourcetype(resourcetype)
+		} else {
+			return fmt.Errorf("invalid type value: %s", typeStr)
+		}
+	}
+
+	if v, ok := d.GetOk("account"); ok {
+		p.SetAccount(v.(string))
+	}
+
+	if v, ok := d.GetOk("domain_id"); ok {
+		p.SetDomainid(v.(string))
+	}
+
+	if v, ok := d.GetOk("project"); ok {
+		p.SetProjectid(v.(string))
+	}
+
+	// Retrieve the resource limits
+	l, err := cs.Limit.ListResourceLimits(p)
+	if err != nil {
+		return fmt.Errorf("Error retrieving resource limits: %s", err)
+	}
+
+	// Generate a unique ID for this data source
+	id := generateDataSourceID(d)
+	d.SetId(id)
+
+	limits := make([]map[string]interface{}, 0, len(l.ResourceLimits))
+
+	// Set the resource data
+	for _, limit := range l.ResourceLimits {
+		limitMap := map[string]interface{}{
+			"resourcetype":     limit.Resourcetype,
+			"resourcetypename": limit.Resourcetypename,
+			"max":              limit.Max,
+		}
+
+		if limit.Account != "" {
+			limitMap["account"] = limit.Account
+		}
+
+		if limit.Domain != "" {
+			limitMap["domain"] = limit.Domain
+		}
+
+		if limit.Domainid != "" {
+			limitMap["domain_id"] = limit.Domainid
+		}
+
+		if limit.Project != "" {
+			limitMap["project"] = limit.Project
+		}
+
+		if limit.Projectid != "" {
+			limitMap["projectid"] = limit.Projectid
+		}
+
+		limits = append(limits, limitMap)
+	}
+
+	if err := d.Set("limits", limits); err != nil {
+		return fmt.Errorf("Error setting limits: %s", err)
+	}
+
+	return nil
+}
+
+// generateDataSourceID generates a unique ID for the data source based on its parameters
+func generateDataSourceID(d *schema.ResourceData) string {
+	var buf bytes.Buffer
+
+	if v, ok := d.GetOk("type"); ok {
+		typeStr := v.(string)
+		if resourcetype, ok := resourceTypeMap[typeStr]; ok {
+			buf.WriteString(fmt.Sprintf("%d-", resourcetype))
+		}
+	}
+
+	if v, ok := d.GetOk("account"); ok {
+		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
+	}
+
+	if v, ok := d.GetOk("domain_id"); ok {
+		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
+	}
+
+	if v, ok := d.GetOk("project"); ok {
+		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
+	}
+
+	// Generate a SHA-256 hash of the buffer content
+	hash := sha256.Sum256(buf.Bytes())
+	return fmt.Sprintf("limits-%s", hex.EncodeToString(hash[:])[:8])
+}
diff --git a/cloudstack/data_source_cloudstack_limits_test.go b/cloudstack/data_source_cloudstack_limits_test.go
new file mode 100644
index 0000000..10b4800
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_limits_test.go
@@ -0,0 +1,198 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccCloudStackLimitsDataSource_basic(t *testing.T) {
+	resourceName := "data.cloudstack_limits.test"
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:  func() { testAccPreCheck(t) },
+		Providers: testAccProviders,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimitsDataSource_basic(),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsDataSourceExists(resourceName),
+					resource.TestCheckResourceAttrSet(resourceName, "limits.#"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimitsDataSource_withType(t *testing.T) {
+	resourceName := "data.cloudstack_limits.test"
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:  func() { testAccPreCheck(t) },
+		Providers: testAccProviders,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimitsDataSource_withType(),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsDataSourceExists(resourceName),
+					resource.TestCheckResourceAttrSet(resourceName, "limits.#"),
+					resource.TestCheckResourceAttr(resourceName, "type", "instance"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimitsDataSource_withDomain(t *testing.T) {
+	resourceName := "data.cloudstack_limits.test"
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:  func() { testAccPreCheck(t) },
+		Providers: testAccProviders,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimitsDataSource_domain + testAccCloudStackLimitsDataSource_withDomain(),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsDataSourceExists(resourceName),
+					resource.TestCheckResourceAttrSet(resourceName, "limits.#"),
+					resource.TestCheckResourceAttrSet(resourceName, "domain_id"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimitsDataSource_withAccount(t *testing.T) {
+	resourceName := "data.cloudstack_limits.test"
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:  func() { testAccPreCheck(t) },
+		Providers: testAccProviders,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimitsDataSource_domain + testAccCloudStackLimitsDataSource_withAccount(),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsDataSourceExists(resourceName),
+					resource.TestCheckResourceAttrSet(resourceName, "limits.#"),
+					resource.TestCheckResourceAttrSet(resourceName, "domain_id"),
+					resource.TestCheckResourceAttrSet(resourceName, "account"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimitsDataSource_multipleTypes(t *testing.T) {
+	resourceName := "data.cloudstack_limits.test"
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:  func() { testAccPreCheck(t) },
+		Providers: testAccProviders,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimitsDataSource_multipleTypes(),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsDataSourceExists(resourceName),
+					resource.TestCheckResourceAttrSet(resourceName, "limits.#"),
+					resource.TestCheckResourceAttr(resourceName, "type", "volume"),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckCloudStackLimitsDataSourceExists(n string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[n]
+		if !ok {
+			return fmt.Errorf("Not found: %s", n)
+		}
+
+		if rs.Primary.ID == "" {
+			return fmt.Errorf("No Limits data source ID is set")
+		}
+
+		return nil
+	}
+}
+
+// Test configurations
+
+const testAccCloudStackLimitsDataSource_domain = `
+resource "cloudstack_domain" "test_domain" {
+  name = "test-domain-limits-ds"
+}
+
+resource "cloudstack_account" "test_account" {
+  username     = "test-account-limits-ds"
+  password     = "password"
+  first_name   = "Test"
+  last_name    = "Account"
+  email        = "test-account-limits-ds@example.com"
+  account_type = 2  # Regular user account type
+  role_id      = "4"  # Regular user role
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+func testAccCloudStackLimitsDataSource_basic() string {
+	return `
+data "cloudstack_limits" "test" {
+}
+`
+}
+
+func testAccCloudStackLimitsDataSource_withType() string {
+	return `
+data "cloudstack_limits" "test" {
+  type = "instance"
+}
+`
+}
+
+func testAccCloudStackLimitsDataSource_withDomain() string {
+	return `
+data "cloudstack_limits" "test" {
+  type      = "volume"
+  domain_id = cloudstack_domain.test_domain.id
+}
+`
+}
+
+func testAccCloudStackLimitsDataSource_withAccount() string {
+	return `
+data "cloudstack_limits" "test" {
+  type      = "snapshot"
+  account   = cloudstack_account.test_account.username
+  domain_id = cloudstack_domain.test_domain.id
+}
+`
+}
+
+func testAccCloudStackLimitsDataSource_multipleTypes() string {
+	return `
+data "cloudstack_limits" "test" {
+  type = "volume"
+}
+`
+}
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index 58c7fa5..3b8c441 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -95,6 +95,7 @@ func Provider() *schema.Provider {
 			"cloudstack_physical_network": dataSourceCloudStackPhysicalNetwork(),
 			"cloudstack_role":             dataSourceCloudstackRole(),
 			"cloudstack_cluster":          dataSourceCloudstackCluster(),
+			"cloudstack_limits":           dataSourceCloudStackLimits(),
 		},
 
 		ResourcesMap: map[string]*schema.Resource{
@@ -147,6 +148,7 @@ func Provider() *schema.Provider {
 			"cloudstack_domain":                         resourceCloudStackDomain(),
 			"cloudstack_network_service_provider":       resourceCloudStackNetworkServiceProvider(),
 			"cloudstack_role":                           resourceCloudStackRole(),
+			"cloudstack_limits":                         resourceCloudStackLimits(),
 		},
 
 		ConfigureFunc: providerConfigure,
diff --git a/cloudstack/resource_cloudstack_account.go b/cloudstack/resource_cloudstack_account.go
index cc5d9c0..db6c1a2 100644
--- a/cloudstack/resource_cloudstack_account.go
+++ b/cloudstack/resource_cloudstack_account.go
@@ -66,7 +66,7 @@ func resourceCloudStackAccount() *schema.Resource {
 				Type:     schema.TypeString,
 				Optional: true,
 			},
-			"domainid": {
+			"domain_id": {
 				Type:     schema.TypeString,
 				Optional: true,
 			},
@@ -84,7 +84,7 @@ func resourceCloudStackAccountCreate(d *schema.ResourceData, meta interface{}) e
 	role_id := d.Get("role_id").(string)
 	account_type := d.Get("account_type").(int)
 	account := d.Get("account").(string)
-	domainid := d.Get("domainid").(string)
+	domain_id := d.Get("domain_id").(string)
 
 	// Create a new parameter struct
 	p := cs.Account.NewCreateAccountParams(email, first_name, last_name, password, username)
@@ -95,7 +95,7 @@ func resourceCloudStackAccountCreate(d *schema.ResourceData, meta interface{}) e
 	} else {
 		p.SetAccount(username)
 	}
-	p.SetDomainid(domainid)
+	p.SetDomainid(domain_id)
 
 	log.Printf("[DEBUG] Creating Account %s", account)
 	a, err := cs.Account.CreateAccount(p)
diff --git a/cloudstack/resource_cloudstack_limits.go b/cloudstack/resource_cloudstack_limits.go
new file mode 100644
index 0000000..be4a689
--- /dev/null
+++ b/cloudstack/resource_cloudstack_limits.go
@@ -0,0 +1,471 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+
+	"github.com/apache/cloudstack-go/v2/cloudstack"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+// resourceTypeMap maps string resource types to their integer values
+var resourceTypeMap = map[string]int{
+	"instance":         0,
+	"ip":               1,
+	"volume":           2,
+	"snapshot":         3,
+	"template":         4,
+	"project":          5,
+	"network":          6,
+	"vpc":              7,
+	"cpu":              8,
+	"memory":           9,
+	"primarystorage":   10,
+	"secondarystorage": 11,
+}
+
+func resourceCloudStackLimits() *schema.Resource {
+	return &schema.Resource{
+		Read:   resourceCloudStackLimitsRead,
+		Update: resourceCloudStackLimitsUpdate,
+		Create: resourceCloudStackLimitsCreate,
+		Delete: resourceCloudStackLimitsDelete,
+		Schema: map[string]*schema.Schema{
+			"type": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+				ValidateFunc: validation.StringInSlice([]string{
+					"instance", "ip", "volume", "snapshot", "template", "project", "network", "vpc",
+					"cpu", "memory", "primarystorage", "secondarystorage",
+				}, false), // false disables case-insensitive matching
+				Description: "The type of resource to update the limits. Available types are: " +
+					"instance, ip, volume, snapshot, template, project, network, vpc, cpu, memory, " +
+					"primarystorage, secondarystorage",
+			},
+			"account": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				ForceNew:    true,
+				Description: "Update resource for a specified account. Must be used with the domain_id parameter.",
+			},
+			"domain_id": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				ForceNew:    true,
+				Description: "Update resource limits for all accounts in specified domain. If used with the account parameter, updates resource limits for a specified account in specified domain.",
+			},
+			"max": {
+				Type:        schema.TypeInt,
+				Optional:    true,
+				Description: "Maximum resource limit. Use -1 for unlimited resource limit. A value of 0 means zero resources are allowed, though the CloudStack API may return -1 for a limit set to 0.",
+			},
+			"configured_max": {
+				Type:        schema.TypeInt,
+				Computed:    true,
+				Description: "Internal field to track the originally configured max value to distinguish between 0 and -1 when CloudStack returns -1.",
+			},
+			"project": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				ForceNew:    true,
+				Description: "Update resource limits for project.",
+			},
+		},
+		Importer: &schema.ResourceImporter{
+			StateContext: resourceCloudStackLimitsImport,
+		},
+	}
+}
+
+// resourceCloudStackLimitsImport parses composite import IDs and sets resource fields accordingly.
+func resourceCloudStackLimitsImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
+	// Expected formats:
+	// - type-account-accountname-domain_id (for account-specific limits)
+	// - type-project-projectid (for project-specific limits)
+	// - type-domain-domain_id (for domain-specific limits)
+
+	log.Printf("[DEBUG] Importing resource with ID: %s", d.Id())
+
+	// First, extract the resource type which is always the first part
+	idParts := strings.SplitN(d.Id(), "-", 2)
+	if len(idParts) < 2 {
+		return nil, fmt.Errorf("unexpected import ID format (%q), expected type-account-accountname-domain_id, type-domain-domain_id, or type-project-projectid", d.Id())
+	}
+
+	// Parse the resource type
+	typeInt, err := strconv.Atoi(idParts[0])
+	if err != nil {
+		return nil, fmt.Errorf("invalid type value in import ID: %s", idParts[0])
+	}
+
+	// Find the string representation for this numeric type
+	var typeStr string
+	for k, v := range resourceTypeMap {
+		if v == typeInt {
+			typeStr = k
+			break
+		}
+	}
+	if typeStr == "" {
+		return nil, fmt.Errorf("unknown type value in import ID: %d", typeInt)
+	}
+	if err := d.Set("type", typeStr); err != nil {
+		return nil, err
+	}
+
+	// Get the original resource ID from the state
+	originalID := d.Id()
+	log.Printf("[DEBUG] Original import ID: %s", originalID)
+
+	// Instead of trying to parse the complex ID, let's create a new resource
+	// and read it from the API to get the correct values
+	cs := meta.(*cloudstack.CloudStackClient)
+
+	// Create a new parameter struct for listing resource limits
+	p := cs.Limit.NewListResourceLimitsParams()
+	p.SetResourcetype(typeInt)
+
+	// Try to determine the resource scope from the ID format
+	remainingID := idParts[1]
+
+	// Extract the resource scope from the ID
+	if strings.HasPrefix(remainingID, "domain-") {
+		// It's a domain-specific limit
+		log.Printf("[DEBUG] Detected domain-specific limit")
+		// We'll use the Read function to get the domain ID from the state
+		// after setting a temporary ID
+		d.SetId(originalID)
+		return []*schema.ResourceData{d}, nil
+	} else if strings.HasPrefix(remainingID, "project-") {
+		// It's a project-specific limit
+		log.Printf("[DEBUG] Detected project-specific limit")
+		// We'll use the Read function to get the project ID from the state
+		// after setting a temporary ID
+		d.SetId(originalID)
+		return []*schema.ResourceData{d}, nil
+	} else if strings.HasPrefix(remainingID, "account-") {
+		// It's an account-specific limit
+		log.Printf("[DEBUG] Detected account-specific limit")
+		// We'll use the Read function to get the account and domain ID from the state
+		// after setting a temporary ID
+		d.SetId(originalID)
+		return []*schema.ResourceData{d}, nil
+	} else {
+		// For backward compatibility, assume it's a global limit
+		log.Printf("[DEBUG] Detected global limit")
+		d.SetId(originalID)
+		return []*schema.ResourceData{d}, nil
+	}
+}
+
+// getResourceType gets the resource type from the type field
+func getResourceType(d *schema.ResourceData) (int, error) {
+	// Check if type is set
+	if v, ok := d.GetOk("type"); ok {
+		typeStr := v.(string)
+		if resourcetype, ok := resourceTypeMap[typeStr]; ok {
+			return resourcetype, nil
+		}
+		return 0, fmt.Errorf("invalid type value: %s", typeStr)
+	}
+
+	return 0, fmt.Errorf("type must be specified")
+}
+
+func resourceCloudStackLimitsCreate(d *schema.ResourceData, meta interface{}) error {
+	cs := meta.(*cloudstack.CloudStackClient)
+
+	resourcetype, err := getResourceType(d)
+	if err != nil {
+		return err
+	}
+
+	account := d.Get("account").(string)
+	domain_id := d.Get("domain_id").(string)
+	projectid := d.Get("project").(string)
+
+	// Validate account and domain parameters
+	if account != "" && domain_id == "" {
+		return fmt.Errorf("domain_id is required when account is specified")
+	}
+
+	// Create a new parameter struct
+	p := cs.Limit.NewUpdateResourceLimitParams(resourcetype)
+	if account != "" {
+		p.SetAccount(account)
+	}
+	if domain_id != "" {
+		p.SetDomainid(domain_id)
+	}
+	// Check for max value - need to handle zero values explicitly
+	maxVal := d.Get("max")
+	if maxVal != nil {
+		maxIntVal := maxVal.(int)
+		log.Printf("[DEBUG] Setting max value to %d", maxIntVal)
+		p.SetMax(int64(maxIntVal))
+
+		// Store the original configured value for later reference
+		// This helps the Read function distinguish between 0 and -1 when CloudStack returns -1
+		if err := d.Set("configured_max", maxIntVal); err != nil {
+			return fmt.Errorf("error storing configured max value: %w", err)
+		}
+	} else {
+		log.Printf("[DEBUG] No max value found in configuration during Create")
+	}
+	if projectid != "" {
+		p.SetProjectid(projectid)
+	}
+
+	log.Printf("[DEBUG] Updating Resource Limit for type %d", resourcetype)
+	_, err = cs.Limit.UpdateResourceLimit(p)
+
+	if err != nil {
+		return fmt.Errorf("Error creating resource limit: %s", err)
+	}
+
+	// Generate a unique ID based on the parameters
+	id := generateResourceID(resourcetype, account, domain_id, projectid)
+	d.SetId(id)
+
+	return resourceCloudStackLimitsRead(d, meta)
+}
+
+// generateResourceID creates a unique ID for the resource based on its parameters
+func generateResourceID(resourcetype int, account, domain_id, projectid string) string {
+	if projectid != "" {
+		return fmt.Sprintf("%d-project-%s", resourcetype, projectid)
+	}
+
+	if account != "" && domain_id != "" {
+		return fmt.Sprintf("%d-account-%s-%s", resourcetype, account, domain_id)
+	}
+
+	if domain_id != "" {
+		return fmt.Sprintf("%d-domain-%s", resourcetype, domain_id)
+	}
+
+	return fmt.Sprintf("%d", resourcetype)
+}
+
+func resourceCloudStackLimitsRead(d *schema.ResourceData, meta interface{}) error {
+	cs := meta.(*cloudstack.CloudStackClient)
+
+	// Get the resourcetype from the type field
+	resourcetype, err := getResourceType(d)
+	if err != nil {
+		// If there's an error getting the type, try to extract it from the ID
+		idParts := strings.Split(d.Id(), "-")
+		if len(idParts) > 0 {
+			if rt, err := strconv.Atoi(idParts[0]); err == nil {
+				resourcetype = rt
+				// Find the string representation for this numeric type
+				for typeStr, typeVal := range resourceTypeMap {
+					if typeVal == rt {
+						if err := d.Set("type", typeStr); err != nil {
+							return fmt.Errorf("error setting type: %s", err)
+						}
+						break
+					}
+				}
+
+				// Handle different ID formats
+				if len(idParts) >= 3 {
+					if idParts[1] == "domain" {
+						// Format: resourcetype-domain-domain_id
+						if err := d.Set("domain_id", idParts[2]); err != nil {
+							return fmt.Errorf("error setting domain_id: %s", err)
+						}
+					} else if idParts[1] == "project" {
+						// Format: resourcetype-project-projectid
+						if err := d.Set("project", idParts[2]); err != nil {
+							return fmt.Errorf("error setting project: %s", err)
+						}
+					} else if idParts[1] == "account" && len(idParts) >= 4 {
+						// Format: resourcetype-account-account-domain_id
+						if err := d.Set("account", idParts[2]); err != nil {
+							return fmt.Errorf("error setting account: %s", err)
+						}
+						if err := d.Set("domain_id", idParts[3]); err != nil {
+							return fmt.Errorf("error setting domain_id: %s", err)
+						}
+					}
+				}
+			}
+		}
+	}
+
+	account := d.Get("account").(string)
+	domain_id := d.Get("domain_id").(string)
+	projectid := d.Get("project").(string)
+
+	// Create a new parameter struct
+	p := cs.Limit.NewListResourceLimitsParams()
+	p.SetResourcetype(resourcetype)
+	if account != "" {
+		p.SetAccount(account)
+	}
+	if domain_id != "" {
+		p.SetDomainid(domain_id)
+	}
+	if projectid != "" {
+		p.SetProjectid(projectid)
+	}
+
+	// Retrieve the resource limits
+	l, err := cs.Limit.ListResourceLimits(p)
+	if err != nil {
+		return fmt.Errorf("error retrieving resource limits: %s", err)
+	}
+
+	if l.Count == 0 {
+		log.Printf("[DEBUG] Resource limit not found")
+		d.SetId("")
+		return nil
+	}
+
+	// Get the first (and should be only) limit from the results
+	limit := l.ResourceLimits[0]
+
+	// Handle the max value - CloudStack may return -1 for both unlimited and zero limits
+	// We need to preserve the original value from the configuration when possible
+	log.Printf("[DEBUG] CloudStack returned max value: %d", limit.Max)
+	if limit.Max == -1 {
+		// CloudStack returns -1 for both unlimited and zero limits
+		// Check if we have the originally configured value stored
+		if configuredMax, hasConfiguredMax := d.GetOk("configured_max"); hasConfiguredMax {
+			configuredValue := configuredMax.(int)
+			log.Printf("[DEBUG] Found configured max value: %d, using it", configuredValue)
+			// Use the originally configured value (0 for zero limit, -1 for unlimited)
+			if err := d.Set("max", configuredValue); err != nil {
+				return fmt.Errorf("error setting max to configured value %d: %w", configuredValue, err)
+			}
+		} else {
+			log.Printf("[DEBUG] No configured max value found, treating -1 as unlimited")
+			// If no configured value is stored, treat -1 as unlimited
+			if err := d.Set("max", -1); err != nil {
+				return fmt.Errorf("error setting max to unlimited (-1): %w", err)
+			}
+		}
+	} else {
+		log.Printf("[DEBUG] Using positive max value from API: %d", limit.Max)
+		// For any positive value, use it directly from the API
+		if err := d.Set("max", int(limit.Max)); err != nil {
+			return fmt.Errorf("error setting max: %w", err)
+		}
+	}
+
+	// Preserve original type configuration if it exists
+	if typeValue, ok := d.GetOk("type"); ok {
+		if err := d.Set("type", typeValue.(string)); err != nil {
+			return fmt.Errorf("error setting type: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func resourceCloudStackLimitsUpdate(d *schema.ResourceData, meta interface{}) error {
+	cs := meta.(*cloudstack.CloudStackClient)
+
+	resourcetype, err := getResourceType(d)
+	if err != nil {
+		return err
+	}
+
+	account := d.Get("account").(string)
+	domain_id := d.Get("domain_id").(string)
+	projectid := d.Get("project").(string)
+
+	// Create a new parameter struct
+	p := cs.Limit.NewUpdateResourceLimitParams(resourcetype)
+	if account != "" {
+		p.SetAccount(account)
+	}
+	if domain_id != "" {
+		p.SetDomainid(domain_id)
+	}
+	if maxVal, ok := d.GetOk("max"); ok {
+		maxIntVal := maxVal.(int)
+		log.Printf("[DEBUG] Setting max value to %d", maxIntVal)
+		p.SetMax(int64(maxIntVal))
+
+		// Store the original configured value for later reference
+		// This helps the Read function distinguish between 0 and -1 when CloudStack returns -1
+		log.Printf("[DEBUG] Storing configured max value in update: %d", maxIntVal)
+		if err := d.Set("configured_max", maxIntVal); err != nil {
+			return fmt.Errorf("error storing configured max value: %w", err)
+		}
+	}
+	if projectid != "" {
+		p.SetProjectid(projectid)
+	}
+
+	log.Printf("[DEBUG] Updating Resource Limit for type %d", resourcetype)
+	_, err = cs.Limit.UpdateResourceLimit(p)
+
+	if err != nil {
+		return fmt.Errorf("Error updating resource limit: %s", err)
+	}
+
+	return resourceCloudStackLimitsRead(d, meta)
+}
+
+func resourceCloudStackLimitsDelete(d *schema.ResourceData, meta interface{}) error {
+	cs := meta.(*cloudstack.CloudStackClient)
+
+	resourcetype, err := getResourceType(d)
+	if err != nil {
+		return err
+	}
+
+	account := d.Get("account").(string)
+	domain_id := d.Get("domain_id").(string)
+	projectid := d.Get("project").(string)
+
+	// Create a new parameter struct
+	p := cs.Limit.NewUpdateResourceLimitParams(resourcetype)
+	if account != "" {
+		p.SetAccount(account)
+	}
+	if domain_id != "" {
+		p.SetDomainid(domain_id)
+	}
+	if projectid != "" {
+		p.SetProjectid(projectid)
+	}
+	p.SetMax(-1) // Set to -1 to remove the limit
+
+	log.Printf("[DEBUG] Removing Resource Limit for type %d", resourcetype)
+	_, err = cs.Limit.UpdateResourceLimit(p)
+
+	if err != nil {
+		return fmt.Errorf("Error removing Resource Limit: %s", err)
+	}
+
+	d.SetId("")
+
+	return nil
+}
diff --git a/cloudstack/resource_cloudstack_limits_test.go b/cloudstack/resource_cloudstack_limits_test.go
new file mode 100644
index 0000000..c847776
--- /dev/null
+++ b/cloudstack/resource_cloudstack_limits_test.go
@@ -0,0 +1,579 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccCloudStackLimits_basic(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_basic,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.foo"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.foo", "type", "instance"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.foo", "max", "10"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_update(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_basic,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.foo"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.foo", "max", "10"),
+				),
+			},
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_update,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.foo"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.foo", "max", "20"),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckCloudStackLimitsExists(n string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		rs, ok := s.RootModule().Resources[n]
+		if !ok {
+			return fmt.Errorf("Not found: %s", n)
+		}
+
+		if rs.Primary.ID == "" {
+			return fmt.Errorf("No Limits ID is set")
+		}
+
+		return nil
+	}
+}
+
+func testAccCheckCloudStackLimitsDestroy(s *terraform.State) error {
+	return nil
+}
+
+const testAccCloudStackLimits_basic = `
+resource "cloudstack_limits" "foo" {
+  type         = "instance"
+  max          = 10
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_update = `
+resource "cloudstack_limits" "foo" {
+  type         = "instance"
+  max          = 20
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+func TestAccCloudStackLimits_domain(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_domain_limit,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.domain_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.domain_limit", "type", "volume"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.domain_limit", "max", "50"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_account(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_account,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.account_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.account_limit", "type", "snapshot"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.account_limit", "max", "100"),
+				),
+			},
+		},
+	})
+}
+
+// func TestAccCloudStackLimits_project(t *testing.T) { #TODO: Need project imported before this will do anything
+// 	resource.Test(t, resource.TestCase{
+// 		PreCheck:     func() { testAccPreCheck(t) },
+// 		Providers:    testAccProviders,
+// 		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+// 		Steps: []resource.TestStep{
+// 			{
+// 				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_project,
+// 				Check: resource.ComposeTestCheckFunc(
+// 					testAccCheckCloudStackLimitsExists("cloudstack_limits.project_limit"),
+// 					resource.TestCheckResourceAttr(
+// 						"cloudstack_limits.project_limit", "type", "primarystorage"),
+// 					resource.TestCheckResourceAttr(
+// 						"cloudstack_limits.project_limit", "max", "1000"),
+// 				),
+// 			},
+// 		},
+// 	})
+// }
+
+func TestAccCloudStackLimits_unlimited(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_unlimited,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.unlimited"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.unlimited", "type", "cpu"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.unlimited", "max", "-1"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_stringType(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_stringType,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.string_type"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.string_type", "type", "network"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.string_type", "max", "30"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_ip(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_ip,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.ip_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.ip_limit", "type", "ip"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.ip_limit", "max", "25"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_template(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_template,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.template_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.template_limit", "type", "template"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.template_limit", "max", "40"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_projectType(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_projectType,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.project_type_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.project_type_limit", "type", "project"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.project_type_limit", "max", "15"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_vpc(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_vpc,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.vpc_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.vpc_limit", "type", "vpc"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.vpc_limit", "max", "10"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_memory(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_memory,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.memory_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.memory_limit", "type", "memory"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.memory_limit", "max", "8192"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_zero(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_zero,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.zero_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.zero_limit", "type", "instance"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.zero_limit", "max", "0"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_secondarystorage(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_secondarystorage,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.secondarystorage_limit"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.secondarystorage_limit", "type", "secondarystorage"),
+					resource.TestCheckResourceAttr(
+						"cloudstack_limits.secondarystorage_limit", "max", "2000"),
+				),
+			},
+		},
+	})
+}
+
+func TestAccCloudStackLimits_import(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_basic,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.foo"),
+				),
+			},
+			{
+				ResourceName:            "cloudstack_limits.foo",
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"domain_id", "type", "max", "configured_max"},
+			},
+		},
+	})
+}
+
+// Test importing domain-specific limits
+func TestAccCloudStackLimits_importDomain(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_domain_limit,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.domain_limit"),
+				),
+			},
+			{
+				ResourceName:            "cloudstack_limits.domain_limit",
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"domain_id", "type", "max", "configured_max"},
+			},
+		},
+	})
+}
+
+// Test importing account-specific limits
+// Note: We're not verifying the state here because the account import is complex
+// and we just want to make sure the import succeeds
+func TestAccCloudStackLimits_importAccount(t *testing.T) {
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudStackLimitsDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCloudStackLimits_domain + testAccCloudStackLimits_account,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudStackLimitsExists("cloudstack_limits.account_limit"),
+				),
+			},
+			{
+				ResourceName:      "cloudstack_limits.account_limit",
+				ImportState:       true,
+				ImportStateVerify: false, // Don't verify the state
+			},
+		},
+	})
+}
+
+// Test configurations for different resource types
+const testAccCloudStackLimits_domain = `
+resource "cloudstack_domain" "test_domain" {
+  name = "test-domain-limits"
+}
+`
+
+const testAccCloudStackLimits_domain_limit = `
+resource "cloudstack_limits" "domain_limit" {
+  type         = "volume"
+  max          = 50
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_account = `
+resource "cloudstack_account" "test_account" {
+  username     = "test-account-limits"
+  password     = "password"
+  first_name   = "Test"
+  last_name    = "Account"
+  email        = "test-account-limits@example.com"
+  account_type = 2  # Regular user account type
+  role_id      = "4"  # Regular user role
+  domain_id    = cloudstack_domain.test_domain.id
+}
+
+resource "cloudstack_limits" "account_limit" {
+  type         = "snapshot"
+  max          = 100
+  account      = cloudstack_account.test_account.username
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+// const testAccCloudStackLimits_project = ` #TODO: Need project imported before this will do anything
+// resource "cloudstack_project" "test_project" {
+//   name         = "test-project-limits"
+//   display_text = "Test Project for Limits"
+//   domain_id    = cloudstack_domain.test_domain.id
+// }
+//
+// resource "cloudstack_limits" "project_limit" {
+//   type         = "primarystorage"
+//   max          = 1000
+//   domain_id    = cloudstack_domain.test_domain.id
+//   projectid    = cloudstack_project.test_project.id
+// }
+// `
+
+const testAccCloudStackLimits_unlimited = `
+resource "cloudstack_limits" "unlimited" {
+  type         = "cpu"
+  max          = -1
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_stringType = `
+resource "cloudstack_limits" "string_type" {
+  type         = "network"
+  max          = 30
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_ip = `
+resource "cloudstack_limits" "ip_limit" {
+  type         = "ip"
+  max          = 25
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_template = `
+resource "cloudstack_limits" "template_limit" {
+  type         = "template"
+  max          = 40
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_projectType = `
+resource "cloudstack_limits" "project_type_limit" {
+  type         = "project"
+  max          = 15
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_vpc = `
+resource "cloudstack_limits" "vpc_limit" {
+  type         = "vpc"
+  max          = 10
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_memory = `
+resource "cloudstack_limits" "memory_limit" {
+  type         = "memory"
+  max          = 8192
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_zero = `
+resource "cloudstack_limits" "zero_limit" {
+  type         = "instance"
+  max          = 0
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_secondarystorage = `
+resource "cloudstack_limits" "secondarystorage_limit" {
+  type         = "secondarystorage"
+  max          = 2000
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_zeroToPositive = `
+resource "cloudstack_limits" "zero_limit" {
+  type         = "instance"
+  max          = 5
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_positiveValue = `
+resource "cloudstack_limits" "positive_limit" {
+  type         = "instance"
+  max          = 15
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_positiveToZero = `
+resource "cloudstack_limits" "positive_limit" {
+  type         = "instance"
+  max          = 0
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_positiveToUnlimited = `
+resource "cloudstack_limits" "positive_limit" {
+  type         = "instance"
+  max          = -1
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
+
+const testAccCloudStackLimits_unlimitedToZero = `
+resource "cloudstack_limits" "unlimited" {
+  type         = "cpu"
+  max          = 0
+  domain_id    = cloudstack_domain.test_domain.id
+}
+`
diff --git a/website/docs/d/limits.html.markdown b/website/docs/d/limits.html.markdown
new file mode 100644
index 0000000..7349e8f
--- /dev/null
+++ b/website/docs/d/limits.html.markdown
@@ -0,0 +1,68 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_limits"
+sidebar_current: "docs-cloudstack-datasource-limits"
+description: |-
+  Gets information about CloudStack resource limits.
+---
+
+# cloudstack_limits
+
+Use this data source to retrieve information about CloudStack resource limits for accounts, domains, and projects.
+
+## Example Usage
+
+```hcl
+# Get all resource limits for a specific domain
+data "cloudstack_limits" "domain_limits" {
+  domain_id = "domain-uuid"
+}
+
+# Get instance limits for a specific account
+data "cloudstack_limits" "account_instance_limits" {
+  type         = "instance"
+  account      = "acct1"
+  domain_id     = "domain-uuid"
+}
+
+# Get primary storage limits for a project
+data "cloudstack_limits" "project_storage_limits" {
+  type         = "primarystorage"
+  project      = "project-uuid"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `type` - (Optional) The type of resource to list the limits. Available types are:
+  * `instance`
+  * `ip`
+  * `volume`
+  * `snapshot`
+  * `template`
+  * `project`
+  * `network`
+  * `vpc`
+  * `cpu`
+  * `memory`
+  * `primarystorage`
+  * `secondarystorage`
+* `account` - (Optional) List resources by account. Must be used with the `domain_id` parameter.
+* `domain_id` - (Optional) List only resources belonging to the domain specified.
+* `project` - (Optional) List resource limits by project.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `limits` - A list of resource limits. Each limit has the following attributes:
+  * `resourcetype` - The type of resource.
+  * `resourcetypename` - The name of the resource type.
+  * `max` - The maximum number of the resource. A value of `-1` indicates unlimited resources. A value of `0` means zero resources are allowed, though the CloudStack API may return `-1` for a limit set to `0`.
+  * `account` - The account of the resource limit.
+  * `domain` - The domain name of the resource limit.
+  * `domain_id` - The domain ID of the resource limit.
+  * `project` - The project name of the resource limit.
+  * `projectid` - The project ID of the resource limit.
diff --git a/website/docs/r/limits.html.markdown b/website/docs/r/limits.html.markdown
new file mode 100644
index 0000000..4c46d67
--- /dev/null
+++ b/website/docs/r/limits.html.markdown
@@ -0,0 +1,88 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_limits"
+sidebar_current: "docs-cloudstack-limits"
+description: |-
+  Provides a CloudStack limits resource.
+---
+
+# cloudstack_limits
+
+Provides a CloudStack limits resource. This can be used to manage resource limits for accounts, domains, and projects within CloudStack.
+
+## Example Usage
+
+```hcl
+# Set instance limit for the root domain
+resource "cloudstack_limits" "instance_limit" {
+  type         = "instance"
+  max          = 20
+}
+
+# Set volume limit for a specific account in a domain
+resource "cloudstack_limits" "volume_limit" {
+  type         = "volume"
+  max          = 50
+  account      = "acct1"
+  domain_id     = "domain-uuid"
+}
+
+# Set primary storage limit for a project
+resource "cloudstack_limits" "storage_limit" {
+  type         = "primarystorage"
+  max          = 1000  # GB
+  project      = "project-uuid"
+}
+
+# Set unlimited CPU limit
+resource "cloudstack_limits" "cpu_unlimited" {
+  type         = "cpu"
+  max          = -1  # Unlimited
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `type` - (Required, ForceNew) The type of resource to update. Available types are:
+  * `instance`
+  * `ip`
+  * `volume`
+  * `snapshot`
+  * `template`
+  * `project`
+  * `network`
+  * `vpc`
+  * `cpu`
+  * `memory`
+  * `primarystorage`
+  * `secondarystorage`
+
+* `account` - (Optional, ForceNew) Update resource for a specified account. Must be used with the `domain_id` parameter.
+* `domain_id` - (Optional, ForceNew) Update resource limits for all accounts in specified domain. If used with the `account` parameter, updates resource limits for a specified account in specified domain.
+* `max` - (Optional) Maximum resource limit. Use `-1` for unlimited resource limit. A value of `0` means zero resources are allowed, though the CloudStack API may return `-1` for a limit set to `0`.
+* `project` - (Optional, ForceNew) Update resource limits for project.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The ID of the resource.
+* `type` - The type of resource.
+* `max` - The maximum number of the resource.
+* `account` - The account of the resource limit.
+* `domain_id` - The domain ID of the resource limit.
+* `project` - The project ID of the resource limit.
+
+## Import
+
+Resource limits can be imported using the resource type (numeric), account, domain ID, and project ID, e.g.
+
+```bash
+terraform import cloudstack_limits.instance_limit 0
+terraform import cloudstack_limits.volume_limit 2-acct1-domain-uuid
+terraform import cloudstack_limits.storage_limit 10-project-uuid
+```
+
+When importing, the numeric resource type is used in the import ID. The provider will automatically convert the numeric type to the corresponding string type after import.