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 <[email protected]>
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 = "[email protected]"
+ 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 = "[email protected]"
+ 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.