diff --git a/cloudstack/resource_cloudstack_security_group.go b/cloudstack/resource_cloudstack_security_group.go index a4ecbcdc..2251f284 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, + }, + + "domainid": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "projectid": { 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 domainid + if account, ok := d.GetOk("account"); ok { + if _, domainOk := d.GetOk("domainid"); !domainOk { + return fmt.Errorf("account parameter requires domainid to be set") + } + // 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) + } + // Create a new parameter struct p := cs.SecurityGroup.NewCreateSecurityGroupParams(name) @@ -76,9 +101,27 @@ 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 { - return err + // Set the account if provided + if account, ok := d.GetOk("account"); ok { + p.SetAccount(account.(string)) + } + + // 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 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) @@ -97,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").(string)), + cloudstack.WithProject(d.Get("projectid").(string)), ) if err != nil { if count == 0 { @@ -113,7 +156,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, "domainid", sg.Domain, sg.Domainid) + setValueOrID(d, "projectid", sg.Project, sg.Projectid) return nil } @@ -125,9 +174,27 @@ 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 { - return err + // Set the account if provided + if account, ok := d.GetOk("account"); ok { + p.SetAccount(account.(string)) + } + + // 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 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 b75776ca..6e977951 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", "projectid", + "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,30 @@ 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" + 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" + domainid = data.cloudstack_domain.root.id +}` 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..b65ee891 100644 --- a/website/docs/r/security_group.html.markdown +++ b/website/docs/r/security_group.html.markdown @@ -19,6 +19,39 @@ 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" + domainid = data.cloudstack_domain.my_domain.id +} +``` + +### 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" + projectid = cloudstack_project.my_project.id +} +``` + ## Argument Reference The following arguments are supported: @@ -29,9 +62,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 `domainid`. Cannot be used with `projectid`. Changing this + forces a new resource to be created. + +* `domainid` - (Optional) The name or ID of the domain to create this security group in. Changing this forces a new resource to be created. +* `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. + ## Attributes Reference The following attributes are exported: