Skip to content

Conversation

ianc769
Copy link
Contributor

@ianc769 ianc769 commented Jul 15, 2025

Adding limits as a terraform managed resource option -> https://cloudstack.apache.org/api/apidocs-4.20/apis/updateResourceLimit.html

Contributes to #82

Using this code for example:

resource "random_uuid" "example" {
}

resource "cloudstack_domain" "example" {
  name             = "example-domain"
  network_domain   = "example.local"
  domain_id        = random_uuid.example.result
  parent_domain_id = "65c9befb-1a1c-11f0-85c0-460f7764ea25"
}

# Set limit in a domain
resource "cloudstack_limits" "cpu_limit" {
  type       = "cpu"
  max        = 42
  domainid   = random_uuid.example.result
  depends_on = [cloudstack_domain.example]

}

# Set volume limit for a specific account in a domain
resource "cloudstack_limits" "volume_limit" {
  type     = "volume"
  max      = 22
  account  = "limits.test"
  domainid = "861bf423-e531-48dc-9bdc-a3183ae0fb0f"
}

# Set primary storage limit for a project
resource "cloudstack_limits" "storage_limit" {
  type      = "primarystorage"
  max       = 1423                                   # GB
  projectid = "c3638a06-9598-4c34-8f89-891beda43eca" // Example project ID
}
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudstack_domain.example will be created
  + resource "cloudstack_domain" "example" {
      + domain_id        = (known after apply)
      + id               = (known after apply)
      + name             = "example-domain"
      + network_domain   = "example.local"
      + parent_domain_id = "65c9befb-1a1c-11f0-85c0-460f7764ea25"
    }

  # cloudstack_limits.cpu_limit will be created
  + resource "cloudstack_limits" "cpu_limit" {
      + domainid = (known after apply)
      + id       = (known after apply)
      + max      = 42
      + type     = "cpu"
    }

  # cloudstack_limits.storage_limit will be created
  + resource "cloudstack_limits" "storage_limit" {
      + id        = (known after apply)
      + max       = 1423
      + projectid = "c3638a06-9598-4c34-8f89-891beda43eca"
      + type      = "primarystorage"
    }

  # cloudstack_limits.volume_limit will be created
  + resource "cloudstack_limits" "volume_limit" {
      + account  = "limits.test"
      + domainid = "861bf423-e531-48dc-9bdc-a3183ae0fb0f"
      + id       = (known after apply)
      + max      = 22
      + type     = "volume"
    }

  # random_uuid.example will be created
  + resource "random_uuid" "example" {
      + id     = (known after apply)
      + result = (known after apply)
    }

Plan: 5 to add, 0 to change, 0 to destroy.
random_uuid.example: Creating...
random_uuid.example: Creation complete after 0s [id=3e435b24-880a-6bae-50a6-fbd5c9be402b]
cloudstack_limits.volume_limit: Creating...
cloudstack_limits.storage_limit: Creating...
cloudstack_domain.example: Creating...
cloudstack_domain.example: Creation complete after 0s [id=3e435b24-880a-6bae-50a6-fbd5c9be402b]
cloudstack_limits.volume_limit: Creation complete after 0s [id=2-account-limits.test-861bf423-e531-48dc-9bdc-a3183ae0fb0f]
cloudstack_limits.cpu_limit: Creating...
cloudstack_limits.storage_limit: Creation complete after 0s [id=10-project-c3638a06-9598-4c34-8f89-891beda43eca]
cloudstack_limits.cpu_limit: Creation complete after 0s [id=8-domain-3e435b24-880a-6bae-50a6-fbd5c9be402b]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

- Implemented data source for retrieving CloudStack resource limits.
- Added resource management for setting and updating resource limits for accounts, domains, and projects.
- Updated documentation for cloudstack_limits with usage examples and argument references.
@DaanHoogland DaanHoogland requested a review from Copilot August 6, 2025 08:04
Copilot

This comment was marked as outdated.

…ted resources

Implimenting copilot suggestions for d.GetOk
@ianc769 ianc769 requested a review from Copilot August 7, 2025 18:17
Copilot

This comment was marked as outdated.

Co-authored-by: Copilot <[email protected]>
@ianc769 ianc769 requested a review from Copilot August 7, 2025 18:27
Copilot

This comment was marked as outdated.

@kiranchavala kiranchavala added this to the v0.6.0 milestone Aug 8, 2025
- update resourceCloudStackLimitsRead to handle different ID formats
- rewrite resourceCloudStackLimitsImport to handle different ID formats
- Support -1 (Unlimited) and 0 (zero) limits
@kiranchavala kiranchavala reopened this Aug 29, 2025
@vishesh92 vishesh92 requested a review from Copilot September 2, 2025 07:47
Optional: true,
Description: "List resources by account. Must be used with the domainid parameter.",
},
"domainid": {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"domainid": {
"domain_id": {

Type: schema.TypeString,
Computed: true,
},
"domainid": {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"domainid": {
"domain_id": {

For consistency with rest of the project

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds CloudStack resource limits management capabilities to the Terraform CloudStack provider by introducing both a data source and resource for managing resource limits for accounts, domains, and projects.

Key changes include:

  • Implementation of cloudstack_limits resource for managing CloudStack resource limits via the updateResourceLimit API
  • Implementation of cloudstack_limits data source for querying existing resource limits
  • Comprehensive test coverage for various limit types, scopes, and edge cases

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
cloudstack/provider.go Registers the new data source and resource with the provider
cloudstack/resource_cloudstack_limits.go Core implementation of the limits resource with CRUD operations and import functionality
cloudstack/data_source_cloudstack_limits.go Data source implementation for querying resource limits
cloudstack/resource_cloudstack_limits_test.go Comprehensive test suite covering various scenarios and resource types
website/docs/r/limits.html.markdown Resource documentation with examples and argument reference
website/docs/d/limits.html.markdown Data source documentation with usage examples

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

if limit.Max == -1 {
// For the zero limit test case, we need to preserve the 0 value
// We'll check if the resource was created with max=0
if d.Get("max").(int) == 0 {
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

Potential panic if 'max' is not set in the resource data. Use GetOk to safely check for the existence and value of the 'max' field before type assertion.

Suggested change
if d.Get("max").(int) == 0 {
if v, ok := d.GetOk("max"); ok && v.(int) == 0 {

Copilot uses AI. Check for mistakes.

// Find the string representation for this numeric type
for typeStr, typeVal := range resourceTypeMap {
if typeVal == rt {
d.Set("type", typeStr)
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Copilot uses AI. Check for mistakes.

if len(idParts) >= 3 {
if idParts[1] == "domain" {
// Format: resourcetype-domain-domainid
d.Set("domainid", idParts[2])
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Copilot uses AI. Check for mistakes.

d.Set("domainid", idParts[2])
} else if idParts[1] == "project" {
// Format: resourcetype-project-projectid
d.Set("projectid", idParts[2])
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Copilot uses AI. Check for mistakes.

Comment on lines +287 to +288
d.Set("account", idParts[2])
d.Set("domainid", idParts[3])
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return values of d.Set() should be checked for errors to ensure the state is properly updated.

Copilot uses AI. Check for mistakes.

Comment on lines +335 to +348
d.Set("max", 0)
} else {
// Otherwise, use -1 to represent unlimited
d.Set("max", limit.Max)
}
} else {
// For any other value, use it directly
d.Set("max", limit.Max)
}

// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Suggested change
d.Set("max", 0)
} else {
// Otherwise, use -1 to represent unlimited
d.Set("max", limit.Max)
}
} else {
// For any other value, use it directly
d.Set("max", limit.Max)
}
// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
if err := d.Set("max", 0); err != nil {
return fmt.Errorf("error setting max to 0: %s", err)
}
} else {
// Otherwise, use -1 to represent unlimited
if err := d.Set("max", limit.Max); err != nil {
return fmt.Errorf("error setting max to unlimited (-1): %s", err)
}
}
} else {
// For any other value, use it directly
if err := d.Set("max", limit.Max); err != nil {
return fmt.Errorf("error setting max: %s", err)
}
}
// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
if err := d.Set("type", v.(string)); err != nil {
return fmt.Errorf("error setting type: %s", err)
}

Copilot uses AI. Check for mistakes.

Comment on lines +335 to +348
d.Set("max", 0)
} else {
// Otherwise, use -1 to represent unlimited
d.Set("max", limit.Max)
}
} else {
// For any other value, use it directly
d.Set("max", limit.Max)
}

// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Suggested change
d.Set("max", 0)
} else {
// Otherwise, use -1 to represent unlimited
d.Set("max", limit.Max)
}
} else {
// For any other value, use it directly
d.Set("max", limit.Max)
}
// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
if err := d.Set("max", 0); err != nil {
return fmt.Errorf("error setting max to 0: %s", err)
}
} else {
// Otherwise, use -1 to represent unlimited
if err := d.Set("max", limit.Max); err != nil {
return fmt.Errorf("error setting max to unlimited (-1): %s", err)
}
}
} else {
// For any other value, use it directly
if err := d.Set("max", limit.Max); err != nil {
return fmt.Errorf("error setting max: %s", err)
}
}
// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
if err := d.Set("type", v.(string)); err != nil {
return fmt.Errorf("error setting type: %s", err)
}

Copilot uses AI. Check for mistakes.

Comment on lines +335 to +348
d.Set("max", 0)
} else {
// Otherwise, use -1 to represent unlimited
d.Set("max", limit.Max)
}
} else {
// For any other value, use it directly
d.Set("max", limit.Max)
}

// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Suggested change
d.Set("max", 0)
} else {
// Otherwise, use -1 to represent unlimited
d.Set("max", limit.Max)
}
} else {
// For any other value, use it directly
d.Set("max", limit.Max)
}
// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
if err := d.Set("max", 0); err != nil {
return fmt.Errorf("failed to set max to 0: %w", err)
}
} else {
// Otherwise, use -1 to represent unlimited
if err := d.Set("max", limit.Max); err != nil {
return fmt.Errorf("failed to set max to unlimited (-1): %w", err)
}
}
} else {
// For any other value, use it directly
if err := d.Set("max", limit.Max); err != nil {
return fmt.Errorf("failed to set max: %w", err)
}
}
// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
if err := d.Set("type", v.(string)); err != nil {
return fmt.Errorf("failed to set type: %w", err)
}

Copilot uses AI. Check for mistakes.

// Only set the type field if it was originally specified in the configuration
if v, ok := d.GetOk("type"); ok {
// Preserve the original case of the type parameter
d.Set("type", v.(string))
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

The return value of d.Set() should be checked for errors to ensure the state is properly updated.

Suggested change
d.Set("type", v.(string))
if err := d.Set("type", v.(string)); err != nil {
return fmt.Errorf("error setting 'type' in state: %s", err)
}

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants