Skip to content

Commit

Permalink
ENG-8938: Add data source to retrieve user groups (#264)
Browse files Browse the repository at this point in the history
* (WIP) Add cyral_role data source

* (WIP) Add test and fix resource details

* Fix data source and improve descriptions

* Fix regexp name filter

* Add documentation example and finish up details

* Fix merge with main
  • Loading branch information
Yowgf authored Aug 6, 2022
1 parent 3f673f4 commit 11073ef
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 0 deletions.
171 changes: 171 additions & 0 deletions cyral/data_source_cyral_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package cyral

import (
"fmt"
"net/http"
"regexp"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/cyralinc/terraform-provider-cyral/client"
)

type GetUserGroupsResponse struct {
Groups []*UserGroup `json:"groups,omitempty"`
}

func (resp *GetUserGroupsResponse) WriteToSchema(d *schema.ResourceData) error {
nameFilter := d.Get("name").(string)
var nameFilterRegexp *regexp.Regexp
if nameFilter != "" {
var err error
if nameFilterRegexp, err = regexp.Compile(nameFilter); err != nil {
return fmt.Errorf("provided name filter is invalid "+
"regexp: %w", err)
}
}

roleList := []interface{}{}
for _, group := range resp.Groups {
if group == nil {
continue
}

if nameFilterRegexp != nil {
if !nameFilterRegexp.MatchString(group.Name) {
continue
}
}

argumentVals := map[string]interface{}{
"id": group.ID,
"name": group.Name,
"description": group.Description,
"roles": group.Roles,
"members": group.Members,
}
ssoGroups := []interface{}{}
for _, mapping := range group.Mappings {
if mapping == nil {
continue
}
ssoGroups = append(ssoGroups, map[string]interface{}{
"id": mapping.Id,
"group_name": mapping.GroupName,
"idp_id": mapping.IdentityProviderId,
"idp_name": mapping.IdentityProviderName,
})
}
argumentVals["sso_groups"] = ssoGroups
roleList = append(roleList, argumentVals)
}
if err := d.Set("role_list", roleList); err != nil {
return err
}
d.SetId(uuid.New().String())
return nil
}

type UserGroup struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Roles []string `json:"roles,omitempty"`
Members []string `json:"members"`
Mappings []*SSOGroup `json:"mappings"`
}

func dataSourceRoleReadConfig() ResourceOperationConfig {
return ResourceOperationConfig{
Name: "RoleDataSourceRead",
HttpMethod: http.MethodGet,
CreateURL: func(d *schema.ResourceData, c *client.Client) string {
return fmt.Sprintf("https://%s/v1/users/groups", c.ControlPlane)
},
NewResponseData: func(_ *schema.ResourceData) ResponseData { return &GetUserGroupsResponse{} },
}
}

func dataSourceRole() *schema.Resource {
return &schema.Resource{
Description: "Retrieve and filter [roles](https://cyral.com/docs/account-administration/acct-manage-cyral-roles/) that exist in the Cyral Control Plane.",
ReadContext: ReadResource(dataSourceRoleReadConfig()),
Schema: map[string]*schema.Schema{
"name": {
Description: "Filter the results by a regular expression (regex) that matches names of existing roles.",
Type: schema.TypeString,
Optional: true,
},
"role_list": {
Description: "List of existing roles satisfying given filter criteria.",
Computed: true,
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "ID of the role in the Cyral environment.",
Type: schema.TypeString,
Computed: true,
},
"name": {
Description: "Role name.",
Type: schema.TypeString,
Computed: true,
},
"description": {
Description: "Role description.",
Type: schema.TypeString,
Computed: true,
},
"roles": {
Description: "IDs of the specific permission roles this role is allowed to assume (e.g. `View Datamaps`, `View Audit Logs`, etc).",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"members": {
Description: "IDs of the users that belong to this role.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"sso_groups": {
Description: `SSO groups mapped to this role. An SSO group mapping means that this role was automatically granted to a user because there's a rule such as "If a user is an 'Engineer' (SSO group) in a specific Identity Provider, make them a 'Super Admin' (role) in Cyral".`,
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the SSO group mapping.",
Type: schema.TypeString,
Computed: true,
},
"group_name": {
Description: "The name of a group configured in the identity provider, e.g. 'Engineer', 'Admin', 'Everyone', etc.",
Type: schema.TypeString,
Computed: true,
},
"idp_id": {
Description: "ID of the identity provider integration.",
Type: schema.TypeString,
Computed: true,
},
"idp_name": {
Description: "Display name of the identity provider integration.",
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
},
},
},
}
}
57 changes: 57 additions & 0 deletions cyral/data_source_cyral_role_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cyral

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

// TODO: currently, we just test that the configs are valid. We need to add ACC
// tests for a full scenario, containing roles, IdPs and user groups (see
// resources `cyral_role` and `cyral_role_sso_groups`. -aholmquist 2022-08-05
/*
func roleDataSourceTestUserGroupsAndRoleNames() ([]*UserGroup, []string) {
return []*UserGroup{
{
Name: "tf-provider-test-user-group-1",
Description: "description-1",
},
{
Name: "tf-provider-test-user-group-2",
Description: "description-2",
},
}, []string{
"tf-provider-test-role-1",
"tf-provider-test-role-2",
}
}
*/

func TestAccRoleDataSource(t *testing.T) {
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: roleDataSourceConfig(
"main_test",
"tf-provider-test-user-group-1",
[]string{}),
},
{
Config: roleDataSourceConfig(
"main_test",
"tf-provider-test-user-group-2",
[]string{}),
},
},
})
}

func roleDataSourceConfig(dsourceName, nameFilter string, dependsOn []string) string {
return fmt.Sprintf(`
data "cyral_role" "%s" {
name = "%s"
depends_on = [%s]
}`, dsourceName, nameFilter, formatAttributes(dependsOn))
}
1 change: 1 addition & 0 deletions cyral/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func Provider() *schema.Provider {
"cyral_datalabel": dataSourceDatalabel(),
"cyral_integration_idp": dataSourceIntegrationIdP(),
"cyral_repository": dataSourceRepository(),
"cyral_role": dataSourceRole(),
"cyral_saml_certificate": dataSourceSAMLCertificate(),
"cyral_saml_configuration": dataSourceSAMLConfiguration(),
"cyral_sidecar_bound_ports": dataSourceSidecarBoundPorts(),
Expand Down
57 changes: 57 additions & 0 deletions docs/data-sources/role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "cyral_role Data Source - cyral"
subcategory: ""
description: |-
Retrieve and filter roles https://cyral.com/docs/account-administration/acct-manage-cyral-roles/ that exist in the Cyral Control Plane.
---

# cyral_role (Data Source)

Retrieve and filter [roles](https://cyral.com/docs/account-administration/acct-manage-cyral-roles/) that exist in the Cyral Control Plane.

## Example Usage

```terraform
data "cyral_role" "admin_roles" {
# Optional. Filter roles with name that matches regular expression.
name = "^.*Admin$"
}
```

<!-- schema generated by tfplugindocs -->

## Schema

### Optional

- `name` (String) Filter the results by a regular expression (regex) that matches names of existing roles.

### Read-Only

- `id` (String) The ID of this resource.
- `role_list` (List of Object) List of existing roles satisfying given filter criteria. (see [below for nested schema](#nestedatt--role_list))

<a id="nestedatt--role_list"></a>

### Nested Schema for `role_list`

Read-Only:

- `description` (String)
- `id` (String)
- `members` (List of String)
- `name` (String)
- `roles` (List of String)
- `sso_groups` (List of Object) (see [below for nested schema](#nestedobjatt--role_list--sso_groups))

<a id="nestedobjatt--role_list--sso_groups"></a>

### Nested Schema for `role_list.sso_groups`

Read-Only:

- `group_name` (String)
- `id` (String)
- `idp_id` (String)
- `idp_name` (String)
4 changes: 4 additions & 0 deletions examples/data-sources/cyral_role/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
data "cyral_role" "admin_roles" {
# Optional. Filter roles with name that matches regular expression.
name = "^.*Admin$"
}

0 comments on commit 11073ef

Please sign in to comment.