This is an automated email from the ASF dual-hosted git repository.
sureshanaparti 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 add8c3a Add cloudstack_role_permission resource (#300)
add8c3a is described below
commit add8c3a4ab26f2aad2b037719e6bc752f9c81007
Author: bddvlpr <[email protected]>
AuthorDate: Mon Aug 17 08:39:51 2026 +0200
Add cloudstack_role_permission resource (#300)
* Add cloudstack_role_permission resource
* Fix ordering of permissions by using a list with (optionally)
authoritative management
---
cloudstack/provider.go | 1 +
cloudstack/resource_cloudstack_role_permission.go | 455 +++++++++++++++++++++
.../resource_cloudstack_role_permission_test.go | 378 +++++++++++++++++
website/cloudstack.erb | 4 +
website/docs/r/role_permission.html.markdown | 67 +++
5 files changed, 905 insertions(+)
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index aea132a..bad42dc 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -164,6 +164,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 0000000..1769f8d
--- /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 0000000..18eeee6
--- /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 1ea7f7e..06b6a7c 100644
--- a/website/cloudstack.erb
+++ b/website/cloudstack.erb
@@ -132,6 +132,10 @@
<li<%=
sidebar_current("docs-cloudstack-resource-role") %>>
<a
href="/docs/providers/cloudstack/r/role.html">cloudstack_role</a>
</li>
+
+ <li<%=
sidebar_current("docs-cloudstack-resource-role-permission") %>>
+ <a
href="/docs/providers/cloudstack/r/role_permission.html">cloudstack_role_permission</a>
+ </li>
</ul>
</li>
</ul>
diff --git a/website/docs/r/role_permission.html.markdown
b/website/docs/r/role_permission.html.markdown
new file mode 100644
index 0000000..090d927
--- /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.