diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 72090147..cf2b0150 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -162,6 +162,7 @@ func Provider() *schema.Provider { "cloudstack_domain": resourceCloudStackDomain(), "cloudstack_network_service_provider": resourceCloudStackNetworkServiceProvider(), "cloudstack_role": resourceCloudStackRole(), + "cloudstack_role_permission": resourceCloudStackRolePermission(), "cloudstack_limits": resourceCloudStackLimits(), "cloudstack_snapshot_policy": resourceCloudStackSnapshotPolicy(), "cloudstack_quota_tariff": resourceCloudStackQuotaTariff(), diff --git a/cloudstack/resource_cloudstack_role_permission.go b/cloudstack/resource_cloudstack_role_permission.go new file mode 100644 index 00000000..1769f8d0 --- /dev/null +++ b/cloudstack/resource_cloudstack_role_permission.go @@ -0,0 +1,455 @@ +// +// 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" + "log" + "sync" + + "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" +) + +var rolePermissionLocks sync.Map + +type rolePermissionSpec struct { + ID string + Rule string + Permission string + Description string +} + +func resourceCloudStackRolePermission() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudStackRolePermissionCreate, + Read: resourceCloudStackRolePermissionRead, + Update: resourceCloudStackRolePermissionUpdate, + Delete: resourceCloudStackRolePermissionDelete, + Schema: map[string]*schema.Schema{ + "role_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "ID of the role the permissions belong to.", + }, + "authoritative": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether permissions not declared in this resource should be deleted.", + }, + "permission": { + Type: schema.TypeList, + Optional: true, + Description: "Ordered list of role permission rules. Rules are evaluated from top to bottom.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "ID of the role permission.", + }, + "rule": { + Type: schema.TypeString, + Required: true, + Description: "The API name or wildcard (e.g. 'list*') the permission applies to.", + }, + "permission": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"allow", "deny"}, false), + Description: "Whether the rule is allowed or denied. Valid options are: allow, deny.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "A description for the role permission.", + }, + }, + }, + }, + }, + } +} + +func resourceCloudStackRolePermissionCreate(d *schema.ResourceData, meta interface{}) error { + roleID := d.Get("role_id").(string) + d.SetId(roleID) + + roleLock := rolePermissionLock(roleID) + roleLock.Lock() + defer roleLock.Unlock() + + if err := reconcileCloudStackRolePermissions(d, meta, nil); err != nil { + return err + } + + return resourceCloudStackRolePermissionRead(d, meta) +} + +func resourceCloudStackRolePermissionRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + roleID := d.Get("role_id").(string) + if roleID == "" { + roleID = d.Id() + } + + rolePermissions, err := listCloudStackRolePermissions(cs, roleID) + if err != nil { + return fmt.Errorf("Error listing Role Permissions: %s", err) + } + + permissionsByID := make(map[string]*cloudstack.RolePermission) + for _, rp := range rolePermissions { + permissionsByID[rp.Id] = rp + } + + var missing bool + var readPermissions []interface{} + used := make(map[string]bool) + + for _, desired := range rolePermissionSpecs(d.Get("permission").([]interface{})) { + var rp *cloudstack.RolePermission + if desired.ID != "" { + rp = permissionsByID[desired.ID] + } + if rp == nil { + rp = findMatchingRolePermission(rolePermissions, desired, used) + } + if rp == nil { + missing = true + readPermissions = append(readPermissions, rolePermissionState(desired)) + continue + } + + used[rp.Id] = true + readPermissions = append(readPermissions, rolePermissionState(rolePermissionSpec{ + ID: rp.Id, + Rule: rp.Rule, + Permission: rp.Permission, + Description: rp.Description, + })) + } + + if err := d.Set("permission", readPermissions); err != nil { + return fmt.Errorf("Error setting Role Permissions: %s", err) + } + + if missing { + log.Printf("[DEBUG] One or more Role Permissions for role %s no longer exist", roleID) + d.SetId("") + } + + return nil +} + +func resourceCloudStackRolePermissionUpdate(d *schema.ResourceData, meta interface{}) error { + roleID := d.Get("role_id").(string) + roleLock := rolePermissionLock(roleID) + roleLock.Lock() + defer roleLock.Unlock() + + var oldPermissions []rolePermissionSpec + if d.HasChange("permission") { + oldRaw, _ := d.GetChange("permission") + oldPermissions = rolePermissionSpecs(oldRaw.([]interface{})) + } + + if err := reconcileCloudStackRolePermissions(d, meta, oldPermissions); err != nil { + return err + } + + return resourceCloudStackRolePermissionRead(d, meta) +} + +func resourceCloudStackRolePermissionDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + roleID := d.Get("role_id").(string) + + roleLock := rolePermissionLock(roleID) + roleLock.Lock() + defer roleLock.Unlock() + + rolePermissions, err := listCloudStackRolePermissions(cs, roleID) + if err != nil { + return fmt.Errorf("Error listing Role Permissions: %s", err) + } + + if d.Get("authoritative").(bool) { + for _, rp := range rolePermissions { + if err := deleteCloudStackRolePermission(cs, rp.Id); err != nil { + return err + } + } + return nil + } + + rolePermissionsByID := make(map[string]*cloudstack.RolePermission) + for _, rp := range rolePermissions { + rolePermissionsByID[rp.Id] = rp + } + + used := make(map[string]bool) + for _, permission := range rolePermissionSpecs(d.Get("permission").([]interface{})) { + ruleID := permission.ID + if ruleID == "" { + if rp := findMatchingRolePermission(rolePermissions, permission, used); rp != nil { + ruleID = rp.Id + } + } + if ruleID == "" || rolePermissionsByID[ruleID] == nil { + continue + } + used[ruleID] = true + if err := deleteCloudStackRolePermission(cs, ruleID); err != nil { + return err + } + } + + return nil +} + +func reconcileCloudStackRolePermissions(d *schema.ResourceData, meta interface{}, oldPermissions []rolePermissionSpec) error { + cs := meta.(*cloudstack.CloudStackClient) + roleID := d.Get("role_id").(string) + + rolePermissions, err := listCloudStackRolePermissions(cs, roleID) + if err != nil { + return fmt.Errorf("Error listing Role Permissions: %s", err) + } + + rolePermissionsByID := make(map[string]*cloudstack.RolePermission) + for _, rp := range rolePermissions { + rolePermissionsByID[rp.Id] = rp + } + + used := make(map[string]bool) + deleted := make(map[string]bool) + managedIDs := make([]string, 0) + managedIDSet := make(map[string]bool) + + for _, desired := range rolePermissionSpecs(d.Get("permission").([]interface{})) { + rp := rolePermissionsByID[desired.ID] + if rp != nil && (rp.Rule != desired.Rule || rp.Description != desired.Description) { + if exactMatch := findMatchingRolePermission(rolePermissions, desired, used); exactMatch != nil { + rp = exactMatch + } else { + if err := deleteCloudStackRolePermission(cs, rp.Id); err != nil { + return err + } + deleted[rp.Id] = true + used[rp.Id] = true + rp = nil + } + } else if rp == nil { + rp = findMatchingRolePermission(rolePermissions, desired, used) + } + + if rp == nil { + rp, err = createCloudStackRolePermission(cs, roleID, desired) + if err != nil { + return err + } + } else if rp.Permission != desired.Permission { + if err := updateCloudStackRolePermission(cs, roleID, rp.Id, desired.Permission); err != nil { + return err + } + } + + used[rp.Id] = true + managedIDs = append(managedIDs, rp.Id) + managedIDSet[rp.Id] = true + } + + oldManagedIDs := make(map[string]bool) + for _, oldPermission := range oldPermissions { + if oldPermission.ID != "" { + oldManagedIDs[oldPermission.ID] = true + } + } + + if d.Get("authoritative").(bool) { + for _, rp := range rolePermissions { + if managedIDSet[rp.Id] || deleted[rp.Id] { + continue + } + if err := deleteCloudStackRolePermission(cs, rp.Id); err != nil { + return err + } + deleted[rp.Id] = true + } + } else { + for oldID := range oldManagedIDs { + if managedIDSet[oldID] || deleted[oldID] { + continue + } + if rolePermissionsByID[oldID] == nil { + continue + } + if err := deleteCloudStackRolePermission(cs, oldID); err != nil { + return err + } + deleted[oldID] = true + } + } + + rolePermissions, err = listCloudStackRolePermissions(cs, roleID) + if err != nil { + return fmt.Errorf("Error listing Role Permissions: %s", err) + } + + ruleOrder := append([]string{}, managedIDs...) + for _, rp := range rolePermissions { + if !managedIDSet[rp.Id] { + ruleOrder = append(ruleOrder, rp.Id) + } + } + + if err := orderCloudStackRolePermissions(cs, roleID, ruleOrder); err != nil { + return err + } + + return nil +} + +func listCloudStackRolePermissions(cs *cloudstack.CloudStackClient, roleID string) ([]*cloudstack.RolePermission, error) { + p := cs.Role.NewListRolePermissionsParams() + p.SetRoleid(roleID) + + l, err := cs.Role.ListRolePermissions(p) + if err != nil { + return nil, err + } + + return l.RolePermissions, nil +} + +func createCloudStackRolePermission(cs *cloudstack.CloudStackClient, roleID string, permission rolePermissionSpec) (*cloudstack.RolePermission, error) { + p := cs.Role.NewCreateRolePermissionParams(permission.Permission, roleID, permission.Rule) + if permission.Description != "" { + p.SetDescription(permission.Description) + } + + log.Printf("[DEBUG] Creating Role Permission %s (%s) for role %s", permission.Rule, permission.Permission, roleID) + r, err := cs.Role.CreateRolePermission(p) + if err != nil { + return nil, fmt.Errorf("Error creating Role Permission: %s", err) + } + + return &cloudstack.RolePermission{ + Id: r.Id, + Roleid: roleID, + Rule: permission.Rule, + Permission: permission.Permission, + Description: permission.Description, + }, nil +} + +func updateCloudStackRolePermission(cs *cloudstack.CloudStackClient, roleID, ruleID, permission string) error { + p := cs.Role.NewUpdateRolePermissionParams(roleID) + p.SetRuleid(ruleID) + p.SetPermission(permission) + + log.Printf("[DEBUG] Updating Role Permission %s", ruleID) + if _, err := cs.Role.UpdateRolePermission(p); err != nil { + return fmt.Errorf("Error updating Role Permission: %s", err) + } + + return nil +} + +func deleteCloudStackRolePermission(cs *cloudstack.CloudStackClient, ruleID string) error { + p := cs.Role.NewDeleteRolePermissionParams(ruleID) + + log.Printf("[DEBUG] Deleting Role Permission %s", ruleID) + if _, err := cs.Role.DeleteRolePermission(p); err != nil { + return fmt.Errorf("Error deleting Role Permission: %s", err) + } + + return nil +} + +func orderCloudStackRolePermissions(cs *cloudstack.CloudStackClient, roleID string, ruleIDs []string) error { + if len(ruleIDs) == 0 { + return nil + } + + p := cs.Role.NewUpdateRolePermissionParams(roleID) + p.SetRuleorder(ruleIDs) + + log.Printf("[DEBUG] Ordering Role Permissions for role %s: %v", roleID, ruleIDs) + if _, err := cs.Role.UpdateRolePermission(p); err != nil { + return fmt.Errorf("Error ordering Role Permissions: %s", err) + } + + return nil +} + +func rolePermissionSpecs(raw []interface{}) []rolePermissionSpec { + permissions := make([]rolePermissionSpec, 0, len(raw)) + for _, item := range raw { + permissionMap := item.(map[string]interface{}) + permissions = append(permissions, rolePermissionSpec{ + ID: rolePermissionString(permissionMap, "id"), + Rule: rolePermissionString(permissionMap, "rule"), + Permission: rolePermissionString(permissionMap, "permission"), + Description: rolePermissionString(permissionMap, "description"), + }) + } + + return permissions +} + +func rolePermissionState(permission rolePermissionSpec) map[string]interface{} { + return map[string]interface{}{ + "id": permission.ID, + "rule": permission.Rule, + "permission": permission.Permission, + "description": permission.Description, + } +} + +func findMatchingRolePermission(rolePermissions []*cloudstack.RolePermission, desired rolePermissionSpec, used map[string]bool) *cloudstack.RolePermission { + for _, rp := range rolePermissions { + if used[rp.Id] { + continue + } + if rp.Rule == desired.Rule && rp.Description == desired.Description { + return rp + } + } + + return nil +} + +func rolePermissionLock(roleID string) *sync.Mutex { + lock, _ := rolePermissionLocks.LoadOrStore(roleID, &sync.Mutex{}) + return lock.(*sync.Mutex) +} + +func rolePermissionString(permission map[string]interface{}, key string) string { + if value, ok := permission[key].(string); ok { + return value + } + + return "" +} diff --git a/cloudstack/resource_cloudstack_role_permission_test.go b/cloudstack/resource_cloudstack_role_permission_test.go new file mode 100644 index 00000000..18eeee63 --- /dev/null +++ b/cloudstack/resource_cloudstack_role_permission_test.go @@ -0,0 +1,378 @@ +// +// 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/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccCloudStackRolePermission_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackRolePermissionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackRolePermission_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionOrder("cloudstack_role_permission.foo", []string{"listVirtualMachines"}), + resource.TestCheckResourceAttr( + "cloudstack_role_permission.foo", "permission.0.rule", "listVirtualMachines"), + resource.TestCheckResourceAttr( + "cloudstack_role_permission.foo", "permission.0.permission", "allow"), + resource.TestCheckResourceAttr( + "cloudstack_role_permission.foo", "permission.0.description", "terraform test role permission"), + ), + }, + { + Config: testAccCloudStackRolePermission_update, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionOrder("cloudstack_role_permission.foo", []string{"listVirtualMachines"}), + resource.TestCheckResourceAttr( + "cloudstack_role_permission.foo", "permission.0.permission", "deny"), + ), + }, + }, + }) +} + +func TestAccCloudStackRolePermission_orderAfterRecreate(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackRolePermissionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackRolePermission_orderWithSpecificRule, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionOrder("cloudstack_role_permission.foo", []string{"listZones", "*"}), + ), + }, + { + Config: testAccCloudStackRolePermission_orderWithoutSpecificRule, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionOrder("cloudstack_role_permission.foo", []string{"*"}), + ), + }, + { + Config: testAccCloudStackRolePermission_orderWithSpecificRule, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionOrder("cloudstack_role_permission.foo", []string{"listZones", "*"}), + ), + }, + }, + }) +} + +func TestAccCloudStackRolePermission_authoritative(t *testing.T) { + var externalRuleID string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackRolePermissionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackRolePermission_subset, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCreateCloudStackRolePermission("cloudstack_role_permission.foo", "listVirtualMachines", "allow", "external role permission", &externalRuleID), + ), + }, + { + Config: testAccCloudStackRolePermission_subset, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionRuleExists("cloudstack_role_permission.foo", &externalRuleID), + ), + }, + { + Config: testAccCloudStackRolePermission_authoritative, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackRolePermissionExists("cloudstack_role_permission.foo"), + testAccCheckCloudStackRolePermissionRuleMissing("cloudstack_role_permission.foo", &externalRuleID), + ), + }, + }, + }) +} + +func testAccCheckCloudStackRolePermissionExists(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 Role Permission ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + p := cs.Role.NewListRolePermissionsParams() + p.SetRoleid(rs.Primary.Attributes["role_id"]) + + if _, err := cs.Role.ListRolePermissions(p); err != nil { + return err + } + + return nil + } +} + +func testAccCheckCloudStackRolePermissionOrder(n string, rules []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rolePermissions, err := testAccListCloudStackRolePermissions(s, n) + if err != nil { + return err + } + if len(rolePermissions) < len(rules) { + return fmt.Errorf("Expected at least %d Role Permissions, got %d", len(rules), len(rolePermissions)) + } + + for i, rule := range rules { + if rolePermissions[i].Rule != rule { + return fmt.Errorf("Expected Role Permission rule %d to be %q, got %q", i, rule, rolePermissions[i].Rule) + } + } + + return nil + } +} + +func testAccCreateCloudStackRolePermission(n, rule, permission, description string, ruleID *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + p := cs.Role.NewCreateRolePermissionParams(permission, rs.Primary.Attributes["role_id"], rule) + p.SetDescription(description) + + r, err := cs.Role.CreateRolePermission(p) + if err != nil { + return err + } + + *ruleID = r.Id + return nil + } +} + +func testAccCheckCloudStackRolePermissionRuleExists(n string, ruleID *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *ruleID == "" { + return fmt.Errorf("No external Role Permission ID is set") + } + + rolePermissions, err := testAccListCloudStackRolePermissions(s, n) + if err != nil { + return err + } + + for _, rp := range rolePermissions { + if rp.Id == *ruleID { + return nil + } + } + + return fmt.Errorf("Role Permission %s not found", *ruleID) + } +} + +func testAccCheckCloudStackRolePermissionRuleMissing(n string, ruleID *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *ruleID == "" { + return fmt.Errorf("No external Role Permission ID is set") + } + + rolePermissions, err := testAccListCloudStackRolePermissions(s, n) + if err != nil { + return err + } + + for _, rp := range rolePermissions { + if rp.Id == *ruleID { + return fmt.Errorf("Role Permission %s still exists", *ruleID) + } + } + + return nil + } +} + +func testAccListCloudStackRolePermissions(s *terraform.State, n string) ([]*cloudstack.RolePermission, error) { + rs, ok := s.RootModule().Resources[n] + if !ok { + return nil, fmt.Errorf("Not found: %s", n) + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + p := cs.Role.NewListRolePermissionsParams() + p.SetRoleid(rs.Primary.Attributes["role_id"]) + + l, err := cs.Role.ListRolePermissions(p) + if err != nil { + return nil, err + } + + return l.RolePermissions, nil +} + +func testAccCheckCloudStackRolePermissionDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_role_permission" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Role Permission ID is set") + } + + p := cs.Role.NewListRolePermissionsParams() + p.SetRoleid(rs.Primary.Attributes["role_id"]) + + if _, err := cs.Role.ListRolePermissions(p); err != nil { + // If the parent role is already gone, the permissions are too. + continue + } + } + + return nil +} + +const testAccCloudStackRolePermission_basic = ` +resource "cloudstack_role" "foo" { + name = "terraform-role" + type = "User" +} + +resource "cloudstack_role_permission" "foo" { + role_id = cloudstack_role.foo.id + + permission { + rule = "listVirtualMachines" + permission = "allow" + description = "terraform test role permission" + } +} +` + +const testAccCloudStackRolePermission_update = ` +resource "cloudstack_role" "foo" { + name = "terraform-role" + type = "User" +} + +resource "cloudstack_role_permission" "foo" { + role_id = cloudstack_role.foo.id + + permission { + rule = "listVirtualMachines" + permission = "deny" + description = "terraform test role permission" + } +} +` + +const testAccCloudStackRolePermission_orderWithSpecificRule = ` +resource "cloudstack_role" "foo" { + name = "terraform-role" + type = "User" +} + +resource "cloudstack_role_permission" "foo" { + role_id = cloudstack_role.foo.id + + permission { + rule = "listZones" + permission = "allow" + } + + permission { + rule = "*" + permission = "deny" + } +} +` + +const testAccCloudStackRolePermission_orderWithoutSpecificRule = ` +resource "cloudstack_role" "foo" { + name = "terraform-role" + type = "User" +} + +resource "cloudstack_role_permission" "foo" { + role_id = cloudstack_role.foo.id + + permission { + rule = "*" + permission = "deny" + } +} +` + +const testAccCloudStackRolePermission_subset = ` +resource "cloudstack_role" "foo" { + name = "terraform-role" + type = "User" +} + +resource "cloudstack_role_permission" "foo" { + role_id = cloudstack_role.foo.id + + permission { + rule = "listZones" + permission = "allow" + } +} +` + +const testAccCloudStackRolePermission_authoritative = ` +resource "cloudstack_role" "foo" { + name = "terraform-role" + type = "User" +} + +resource "cloudstack_role_permission" "foo" { + role_id = cloudstack_role.foo.id + authoritative = true + + permission { + rule = "listZones" + permission = "allow" + } +} +` diff --git a/website/cloudstack.erb b/website/cloudstack.erb index 1ea7f7ec..06b6a7c3 100644 --- a/website/cloudstack.erb +++ b/website/cloudstack.erb @@ -132,6 +132,10 @@ > cloudstack_role + + > + cloudstack_role_permission + diff --git a/website/docs/r/role_permission.html.markdown b/website/docs/r/role_permission.html.markdown new file mode 100644 index 00000000..090d9270 --- /dev/null +++ b/website/docs/r/role_permission.html.markdown @@ -0,0 +1,67 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_role_permission" +description: |- + Manages ordered role permissions for a role. +--- + +# cloudstack_role_permission + +Manages an ordered list of role permissions for a role. A role permission is a +single rule that allows or denies access to an API or wildcard set of APIs. + +Rules belonging to the same role are evaluated in order, and the first matching +rule wins. This resource stores that order explicitly and reapplies it after +rules are added, removed, or recreated. + +By default, only the permissions declared in this resource are managed. +Undeclared permissions on the role are preserved and ordered after the declared +permissions. Set `authoritative = true` to delete undeclared permissions and +make the CloudStack role permission list exactly match this resource. + +## Example Usage + +```hcl +resource "cloudstack_role" "custom" { + name = "custom-role" + type = "User" +} + +resource "cloudstack_role_permission" "custom" { + role_id = cloudstack_role.custom.id + + permission { + rule = "listVirtualMachines" + permission = "allow" + description = "Allow listing virtual machines" + } + + permission { + rule = "*" + permission = "deny" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `role_id` - (Required) ID of the role the permissions belong to. Changing this + forces a new resource to be created. +* `authoritative` - (Optional) Whether permissions not declared in this resource + should be deleted. Defaults to `false`. +* `permission` - (Optional) Ordered list of role permission rules. Each block + supports the following: + * `rule` - (Required) The API name or a wildcard (e.g. `list*` or `*`) the + rule applies to. + * `permission` - (Required) Whether the rule is allowed or denied. Valid + options are: `allow`, `deny`. + * `description` - (Optional) A description for the role permission. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The role ID. +* `permission.*.id` - The ID of each role permission rule.