From be0fc4a745ac2c1753c4f37911291c5a5b65aa2b Mon Sep 17 00:00:00 2001 From: Ian Crouch Date: Thu, 6 Nov 2025 11:30:38 -0500 Subject: [PATCH 1/2] rename project to project_id since thats --- .../resource_cloudstack_security_group.go | 65 +++++++++++++++++-- ...resource_cloudstack_security_group_test.go | 61 +++++++++++++++++ cloudstack/resources.go | 13 ++++ website/docs/r/security_group.html.markdown | 36 +++++++++- 4 files changed, 167 insertions(+), 8 deletions(-) diff --git a/cloudstack/resource_cloudstack_security_group.go b/cloudstack/resource_cloudstack_security_group.go index a4ecbcdc..5d37cd0c 100644 --- a/cloudstack/resource_cloudstack_security_group.go +++ b/cloudstack/resource_cloudstack_security_group.go @@ -51,7 +51,20 @@ func resourceCloudStackSecurityGroup() *schema.Resource { ForceNew: true, }, - "project": { + "account": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "domain": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "project_id": { Type: schema.TypeString, Optional: true, Computed: true, @@ -66,6 +79,18 @@ func resourceCloudStackSecurityGroupCreate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) + // Validate that account is used with domain + if account, ok := d.GetOk("account"); ok { + if _, domainOk := d.GetOk("domain"); !domainOk { + return fmt.Errorf("account parameter requires domain to be set") + } + // Account and project are mutually exclusive + if _, projectOk := d.GetOk("project_id"); projectOk { + return fmt.Errorf("account and project parameters are mutually exclusive") + } + log.Printf("[DEBUG] Creating security group %s for account %s", name, account) + } + // Create a new parameter struct p := cs.SecurityGroup.NewCreateSecurityGroupParams(name) @@ -76,11 +101,21 @@ func resourceCloudStackSecurityGroupCreate(d *schema.ResourceData, meta interfac p.SetDescription(name) } - // If there is a project supplied, we retrieve and set the project id - if err := setProjectid(p, cs, d); err != nil { + // Set the account if provided + if account, ok := d.GetOk("account"); ok { + p.SetAccount(account.(string)) + } + + // If there is a domain supplied, we retrieve and set the domain id + if err := setDomainid(p, cs, d); err != nil { return err } + // If there is a project_id supplied, we set it directly + if projectID, ok := d.GetOk("project_id"); ok { + p.SetProjectid(projectID.(string)) + } + r, err := cs.SecurityGroup.CreateSecurityGroup(p) if err != nil { return fmt.Errorf("Error creating security group %s: %s", name, err) @@ -97,7 +132,7 @@ func resourceCloudStackSecurityGroupRead(d *schema.ResourceData, meta interface{ // Get the security group details sg, count, err := cs.SecurityGroup.GetSecurityGroupByID( d.Id(), - cloudstack.WithProject(d.Get("project").(string)), + cloudstack.WithProject(d.Get("project_id").(string)), ) if err != nil { if count == 0 { @@ -113,7 +148,13 @@ func resourceCloudStackSecurityGroupRead(d *schema.ResourceData, meta interface{ d.Set("name", sg.Name) d.Set("description", sg.Description) - setValueOrID(d, "project", sg.Project, sg.Projectid) + // Only set account if it was explicitly configured + if _, ok := d.GetOk("account"); ok { + d.Set("account", sg.Account) + } + + setValueOrID(d, "domain", sg.Domain, sg.Domainid) + setValueOrID(d, "project_id", sg.Project, sg.Projectid) return nil } @@ -125,11 +166,21 @@ func resourceCloudStackSecurityGroupDelete(d *schema.ResourceData, meta interfac p := cs.SecurityGroup.NewDeleteSecurityGroupParams() p.SetId(d.Id()) - // If there is a project supplied, we retrieve and set the project id - if err := setProjectid(p, cs, d); err != nil { + // Set the account if provided + if account, ok := d.GetOk("account"); ok { + p.SetAccount(account.(string)) + } + + // If there is a domain supplied, we retrieve and set the domain id + if err := setDomainid(p, cs, d); err != nil { return err } + // If there is a project_id supplied, we set it directly + if projectID, ok := d.GetOk("project_id"); ok { + p.SetProjectid(projectID.(string)) + } + // Delete the security group _, err := cs.SecurityGroup.DeleteSecurityGroup(p) if err != nil { diff --git a/cloudstack/resource_cloudstack_security_group_test.go b/cloudstack/resource_cloudstack_security_group_test.go index b75776ca..9492b9f5 100644 --- a/cloudstack/resource_cloudstack_security_group_test.go +++ b/cloudstack/resource_cloudstack_security_group_test.go @@ -47,6 +47,47 @@ func TestAccCloudStackSecurityGroup_basic(t *testing.T) { }) } +func TestAccCloudStackSecurityGroup_project(t *testing.T) { + var sg cloudstack.SecurityGroup + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSecurityGroup_project, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSecurityGroupExists( + "cloudstack_security_group.foo", &sg), + resource.TestCheckResourceAttrPair( + "cloudstack_security_group.foo", "project_id", + "cloudstack_project.test", "id"), + ), + }, + }, + }) +} + +func TestAccCloudStackSecurityGroup_account(t *testing.T) { + var sg cloudstack.SecurityGroup + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackSecurityGroup_account, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackSecurityGroupExists( + "cloudstack_security_group.foo", &sg), + resource.TestCheckResourceAttr( + "cloudstack_security_group.foo", "account", "admin"), + ), + }, + }, + }) +} + func TestAccCloudStackSecurityGroup_import(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -136,3 +177,23 @@ resource "cloudstack_security_group" "foo" { name = "terraform-security-group" description = "terraform-security-group-text" }` + +const testAccCloudStackSecurityGroup_project = ` +resource "cloudstack_project" "test" { + name = "terraform-security-group-test-project" + displaytext = "Terraform Security Group Test Project" +} + +resource "cloudstack_security_group" "foo" { + name = "terraform-security-group-project" + description = "terraform-security-group-project-text" + project_id = cloudstack_project.test.id +}` + +const testAccCloudStackSecurityGroup_account = ` +resource "cloudstack_security_group" "foo" { + name = "terraform-security-group-account" + description = "terraform-security-group-account-text" + account = "admin" + domain = "ROOT" +}` diff --git a/cloudstack/resources.go b/cloudstack/resources.go index 5a75b77d..6f07170e 100644 --- a/cloudstack/resources.go +++ b/cloudstack/resources.go @@ -162,6 +162,19 @@ func setProjectid(p cloudstack.ProjectIDSetter, cs *cloudstack.CloudStackClient, return nil } +// If there is a domain supplied, we retrieve and set the domain id +func setDomainid(p cloudstack.DomainIDSetter, cs *cloudstack.CloudStackClient, d *schema.ResourceData) error { + if domain, ok := d.GetOk("domain"); ok { + domainid, e := retrieveID(cs, "domain", domain.(string)) + if e != nil { + return e.Error() + } + p.SetDomainid(domainid) + } + + return nil +} + // importStatePassthrough is a generic importer with project support. func importStatePassthrough(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { // Try to split the ID to extract the optional project name. diff --git a/website/docs/r/security_group.html.markdown b/website/docs/r/security_group.html.markdown index a07d557c..0535a10c 100644 --- a/website/docs/r/security_group.html.markdown +++ b/website/docs/r/security_group.html.markdown @@ -19,6 +19,32 @@ resource "cloudstack_security_group" "default" { } ``` +### With Account and Domain + +```hcl +resource "cloudstack_security_group" "account_sg" { + name = "allow_web" + description = "Allow access to HTTP and HTTPS" + account = "my-account" + domain = "example-domain" +} +``` + +### With Project + +```hcl +resource "cloudstack_project" "my_project" { + name = "my-project" + displaytext = "My Project" +} + +resource "cloudstack_security_group" "project_sg" { + name = "allow_web" + description = "Allow access to HTTP and HTTPS" + project_id = cloudstack_project.my_project.id +} +``` + ## Argument Reference The following arguments are supported: @@ -29,9 +55,17 @@ The following arguments are supported: * `description` - (Optional) The description of the security group. Changing this forces a new resource to be created. -* `project` - (Optional) The name or ID of the project to create this security +* `account` - (Optional) The account name to create the security group for. + Must be used with `domain`. Cannot be used with `project_id`. Changing this + forces a new resource to be created. + +* `domain` - (Optional) The name or ID of the domain to create this security group in. Changing this forces a new resource to be created. +* `project_id` - (Optional) The ID of the project to create this security + group in. Cannot be used with `account`. Changing this forces a new + resource to be created. + ## Attributes Reference The following attributes are exported: From 9a27f5ac477a34487c82b80c44653de47862bc00 Mon Sep 17 00:00:00 2001 From: Ian Crouch Date: Thu, 6 Nov 2025 12:25:35 -0500 Subject: [PATCH 2/2] use domainid and projectid like the api --- .../resource_cloudstack_security_group.go | 62 ++++++++++++------- ...resource_cloudstack_security_group_test.go | 17 +++-- website/docs/r/security_group.html.markdown | 17 +++-- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/cloudstack/resource_cloudstack_security_group.go b/cloudstack/resource_cloudstack_security_group.go index 5d37cd0c..2251f284 100644 --- a/cloudstack/resource_cloudstack_security_group.go +++ b/cloudstack/resource_cloudstack_security_group.go @@ -57,14 +57,14 @@ func resourceCloudStackSecurityGroup() *schema.Resource { ForceNew: true, }, - "domain": { + "domainid": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - "project_id": { + "projectid": { Type: schema.TypeString, Optional: true, Computed: true, @@ -79,14 +79,14 @@ func resourceCloudStackSecurityGroupCreate(d *schema.ResourceData, meta interfac name := d.Get("name").(string) - // Validate that account is used with domain + // Validate that account is used with domainid if account, ok := d.GetOk("account"); ok { - if _, domainOk := d.GetOk("domain"); !domainOk { - return fmt.Errorf("account parameter requires domain to be set") + if _, domainOk := d.GetOk("domainid"); !domainOk { + return fmt.Errorf("account parameter requires domainid to be set") } - // Account and project are mutually exclusive - if _, projectOk := d.GetOk("project_id"); projectOk { - return fmt.Errorf("account and project parameters are mutually exclusive") + // Account and projectid are mutually exclusive + if _, projectOk := d.GetOk("projectid"); projectOk { + return fmt.Errorf("account and projectid parameters are mutually exclusive") } log.Printf("[DEBUG] Creating security group %s for account %s", name, account) } @@ -106,14 +106,22 @@ func resourceCloudStackSecurityGroupCreate(d *schema.ResourceData, meta interfac p.SetAccount(account.(string)) } - // If there is a domain supplied, we retrieve and set the domain id - if err := setDomainid(p, cs, d); err != nil { - return err + // If there is a domainid supplied, retrieve and set the domain id (supports both names and IDs) + if domain, ok := d.GetOk("domainid"); ok { + domainID, err := retrieveID(cs, "domain", domain.(string)) + if err != nil { + return err.Error() + } + p.SetDomainid(domainID) } - // If there is a project_id supplied, we set it directly - if projectID, ok := d.GetOk("project_id"); ok { - p.SetProjectid(projectID.(string)) + // If there is a projectid supplied, retrieve and set the project id (supports both names and IDs) + if project, ok := d.GetOk("projectid"); ok { + projectID, err := retrieveID(cs, "project", project.(string)) + if err != nil { + return err.Error() + } + p.SetProjectid(projectID) } r, err := cs.SecurityGroup.CreateSecurityGroup(p) @@ -132,7 +140,7 @@ func resourceCloudStackSecurityGroupRead(d *schema.ResourceData, meta interface{ // Get the security group details sg, count, err := cs.SecurityGroup.GetSecurityGroupByID( d.Id(), - cloudstack.WithProject(d.Get("project_id").(string)), + cloudstack.WithProject(d.Get("projectid").(string)), ) if err != nil { if count == 0 { @@ -153,8 +161,8 @@ func resourceCloudStackSecurityGroupRead(d *schema.ResourceData, meta interface{ d.Set("account", sg.Account) } - setValueOrID(d, "domain", sg.Domain, sg.Domainid) - setValueOrID(d, "project_id", sg.Project, sg.Projectid) + setValueOrID(d, "domainid", sg.Domain, sg.Domainid) + setValueOrID(d, "projectid", sg.Project, sg.Projectid) return nil } @@ -171,14 +179,22 @@ func resourceCloudStackSecurityGroupDelete(d *schema.ResourceData, meta interfac p.SetAccount(account.(string)) } - // If there is a domain supplied, we retrieve and set the domain id - if err := setDomainid(p, cs, d); err != nil { - return err + // If there is a domainid supplied, retrieve and set the domain id (supports both names and IDs) + if domain, ok := d.GetOk("domainid"); ok { + domainID, err := retrieveID(cs, "domain", domain.(string)) + if err != nil { + return err.Error() + } + p.SetDomainid(domainID) } - // If there is a project_id supplied, we set it directly - if projectID, ok := d.GetOk("project_id"); ok { - p.SetProjectid(projectID.(string)) + // If there is a projectid supplied, retrieve and set the project id (supports both names and IDs) + if project, ok := d.GetOk("projectid"); ok { + projectID, err := retrieveID(cs, "project", project.(string)) + if err != nil { + return err.Error() + } + p.SetProjectid(projectID) } // Delete the security group diff --git a/cloudstack/resource_cloudstack_security_group_test.go b/cloudstack/resource_cloudstack_security_group_test.go index 9492b9f5..6e977951 100644 --- a/cloudstack/resource_cloudstack_security_group_test.go +++ b/cloudstack/resource_cloudstack_security_group_test.go @@ -60,7 +60,7 @@ func TestAccCloudStackSecurityGroup_project(t *testing.T) { testAccCheckCloudStackSecurityGroupExists( "cloudstack_security_group.foo", &sg), resource.TestCheckResourceAttrPair( - "cloudstack_security_group.foo", "project_id", + "cloudstack_security_group.foo", "projectid", "cloudstack_project.test", "id"), ), }, @@ -187,13 +187,20 @@ resource "cloudstack_project" "test" { resource "cloudstack_security_group" "foo" { name = "terraform-security-group-project" description = "terraform-security-group-project-text" - project_id = cloudstack_project.test.id + projectid = cloudstack_project.test.id }` const testAccCloudStackSecurityGroup_account = ` +data "cloudstack_domain" "root" { + filter { + name = "name" + value = "ROOT" + } +} + resource "cloudstack_security_group" "foo" { name = "terraform-security-group-account" - description = "terraform-security-group-account-text" - account = "admin" - domain = "ROOT" + description = "terraform-security-group-account-text" + account = "admin" + domainid = data.cloudstack_domain.root.id }` diff --git a/website/docs/r/security_group.html.markdown b/website/docs/r/security_group.html.markdown index 0535a10c..b65ee891 100644 --- a/website/docs/r/security_group.html.markdown +++ b/website/docs/r/security_group.html.markdown @@ -22,11 +22,18 @@ resource "cloudstack_security_group" "default" { ### With Account and Domain ```hcl +data "cloudstack_domain" "my_domain" { + filter { + name = "name" + value = "ROOT" + } +} + resource "cloudstack_security_group" "account_sg" { name = "allow_web" description = "Allow access to HTTP and HTTPS" account = "my-account" - domain = "example-domain" + domainid = data.cloudstack_domain.my_domain.id } ``` @@ -41,7 +48,7 @@ resource "cloudstack_project" "my_project" { resource "cloudstack_security_group" "project_sg" { name = "allow_web" description = "Allow access to HTTP and HTTPS" - project_id = cloudstack_project.my_project.id + projectid = cloudstack_project.my_project.id } ``` @@ -56,13 +63,13 @@ The following arguments are supported: this forces a new resource to be created. * `account` - (Optional) The account name to create the security group for. - Must be used with `domain`. Cannot be used with `project_id`. Changing this + Must be used with `domainid`. Cannot be used with `projectid`. Changing this forces a new resource to be created. -* `domain` - (Optional) The name or ID of the domain to create this security +* `domainid` - (Optional) The name or ID of the domain to create this security group in. Changing this forces a new resource to be created. -* `project_id` - (Optional) The ID of the project to create this security +* `projectid` - (Optional) The name or ID of the project to create this security group in. Cannot be used with `account`. Changing this forces a new resource to be created.