diff --git a/docs/data-sources/role.md b/docs/data-sources/role.md new file mode 100644 index 00000000..23d66ae2 --- /dev/null +++ b/docs/data-sources/role.md @@ -0,0 +1,62 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "boundary_role Data Source - terraform-provider-boundary" +subcategory: "" +description: |- + The boundary_role data source allows you to find a Boundary role. +--- + +# boundary_role (Data Source) + +The boundary_role data source allows you to find a Boundary role. + +## Example Usage + +```terraform +# Role from the global scope +data "boundary_role" "global_role" { + name = "global_role_one" +} + +# Role from an org scope +data "boundary_scope" "org" { + name = "org_one" + scope_id = "global" +} + +data "boundary_role" "org_role" { + name = "org_role_one" + scope_id = data.boundary_scope.org.id +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the role to retrieve. + +### Optional + +- `scope_id` (String) The scope ID in which the resource is created. Defaults `global` if unset. + +### Read-Only + +- `description` (String) The description of the retrieved role. +- `grant_scope_ids` (Set of String) The list of scope IDs for the role's grants. +- `grant_strings` (Set of String) The list of stringified grants for the role. +- `id` (String) The ID of the retrieved role. +- `principal_ids` (Set of String) The list of principal (user or group) IDs added as principals on the role. +- `scope` (List of Object) (see [below for nested schema](#nestedatt--scope)) + + +### Nested Schema for `scope` + +Read-Only: + +- `description` (String) +- `id` (String) +- `name` (String) +- `parent_scope_id` (String) +- `type` (String) diff --git a/examples/data-sources/boundary_role/data-source.tf b/examples/data-sources/boundary_role/data-source.tf new file mode 100644 index 00000000..f2307465 --- /dev/null +++ b/examples/data-sources/boundary_role/data-source.tf @@ -0,0 +1,15 @@ +# Role from the global scope +data "boundary_role" "global_role" { + name = "global_role_one" +} + +# Role from an org scope +data "boundary_scope" "org" { + name = "org_one" + scope_id = "global" +} + +data "boundary_role" "org_role" { + name = "org_role_one" + scope_id = data.boundary_scope.org.id +} diff --git a/internal/provider/data_source_role.go b/internal/provider/data_source_role.go new file mode 100644 index 00000000..40abdc51 --- /dev/null +++ b/internal/provider/data_source_role.go @@ -0,0 +1,162 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "net/http" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/roles" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func dataSourceRole() *schema.Resource { + return &schema.Resource{ + Description: "The boundary_role data source allows you to find a Boundary role.", + ReadContext: dataSourceRoleRead, + + Schema: map[string]*schema.Schema{ + NameKey: { + Description: "The name of the role to retrieve.", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + ScopeIdKey: { + Description: "The scope ID in which the resource is created. Defaults `global` if unset.", + Type: schema.TypeString, + Optional: true, + Default: "global", + ValidateFunc: validation.StringIsNotEmpty, + }, + IDKey: { + Description: "The ID of the retrieved role.", + Type: schema.TypeString, + Computed: true, + }, + DescriptionKey: { + Description: "The description of the retrieved role.", + Type: schema.TypeString, + Computed: true, + }, + ScopeKey: { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + IDKey: { + Type: schema.TypeString, + Computed: true, + }, + NameKey: { + Type: schema.TypeString, + Computed: true, + }, + TypeKey: { + Type: schema.TypeString, + Computed: true, + }, + DescriptionKey: { + Type: schema.TypeString, + Computed: true, + }, + ParentScopeIdKey: { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + rolePrincipalIdsKey: { + Description: "The list of principal (user or group) IDs added as principals on the role.", + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + roleGrantStringsKey: { + Description: "The list of stringified grants for the role.", + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + roleGrantScopeIdsKey: { + Description: "The list of scope IDs for the role's grants.", + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + }, + } +} + +func dataSourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + md := meta.(*metaData) + + name := d.Get(NameKey).(string) + scopeId := d.Get(ScopeIdKey).(string) + + rcl := roles.NewClient(md.client) + rolesList, err := rcl.List(ctx, scopeId, + roles.WithFilter(FilterWithItemNameMatches(name)), + ) + if err != nil { + return diag.Errorf("error calling list role: %v", err) + } + roles := rolesList.GetItems() + if roles == nil { + return diag.Errorf("no roles found") + } + if len(roles) == 0 { + return diag.Errorf("no matching role found") + } + if len(roles) > 1 { + return diag.Errorf("error found more than 1 role") + } + + rrr, err := rcl.Read(ctx, roles[0].Id) + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound { + d.SetId("") + return nil + } + return diag.Errorf("error calling read role: %v", err) + } + if rrr == nil { + return diag.Errorf("role nil after read") + } + + if err := setFromRoleRead(d, *rrr.Item); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func setFromRoleRead(d *schema.ResourceData, role roles.Role) error { + if err := d.Set(NameKey, role.Name); err != nil { + return err + } + if err := d.Set(DescriptionKey, role.Description); err != nil { + return err + } + if err := d.Set(ScopeIdKey, role.ScopeId); err != nil { + return err + } + if err := d.Set(roleGrantStringsKey, role.GrantStrings); err != nil { + return err + } + if err := d.Set(roleGrantScopeIdsKey, role.GrantScopeIds); err != nil { + return err + } + if err := d.Set(rolePrincipalIdsKey, role.PrincipalIds); err != nil { + return err + } + + d.Set(ScopeKey, flattenScopeInfo(role.Scope)) + d.SetId(role.Id) + return nil +} diff --git a/internal/provider/data_source_role_test.go b/internal/provider/data_source_role_test.go new file mode 100644 index 00000000..d982d44a --- /dev/null +++ b/internal/provider/data_source_role_test.go @@ -0,0 +1,126 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/boundary/testing/controller" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + testRoleName = "my_role" +) + +var roleReadGlobal = fmt.Sprintf(` +resource "boundary_user" "user" { + name = "my_user" + scope_id = "global" +} + +resource "boundary_role" "role" { + name = "%s" + description = "test role global" + scope_id = "global" + grant_strings = [ + "ids=*;type=*;actions=read" + ] + principal_ids = [ boundary_user.user.id ] +} + +data "boundary_role" "role" { + depends_on = [ boundary_role.role ] + name = "%s" +} +`, testRoleName, testRoleName) + +var roleReadOrg = fmt.Sprintf(` +resource "boundary_user" "user" { + name = "my_user" + scope_id = "global" +} + +resource "boundary_role" "role" { + name = "%s" + description = "test role org" + scope_id = boundary_scope.org1.id + grant_strings = [ + "ids=*;type=*;actions=read" + ] + principal_ids = [ boundary_user.user.id ] +} + +data "boundary_role" "role" { + depends_on = [ boundary_role.role ] + name = "%s" + scope_id = boundary_scope.org1.id +} +`, testRoleName, testRoleName) + +func TestAccRoleReadGlobal(t *testing.T) { + tc := controller.NewTestController(t, tcConfig...) + defer tc.Shutdown() + url := tc.ApiAddrs()[0] + + dataSourceName := "data.boundary_role.role" + + var provider *schema.Provider + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories(&provider), + Steps: []resource.TestStep{ + { + Config: testConfig(url, roleReadGlobal), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, IDKey), + resource.TestCheckResourceAttrSet(dataSourceName, ScopeIdKey), + resource.TestCheckResourceAttr(dataSourceName, NameKey, testRoleName), + resource.TestCheckResourceAttrSet(dataSourceName, DescriptionKey), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.#", roleGrantStringsKey), "1"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.0", roleGrantStringsKey), "ids=*;type=*;actions=read"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.#", roleGrantScopeIdsKey), "1"), + resource.TestCheckResourceAttrSet(dataSourceName, "scope.0.id"), + resource.TestCheckResourceAttr(dataSourceName, "scope.0.name", "global"), + resource.TestCheckResourceAttr(dataSourceName, "scope.0.type", "global"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.#", rolePrincipalIdsKey), "1"), + resource.TestCheckResourceAttrPair(dataSourceName, fmt.Sprintf("%s.0", rolePrincipalIdsKey), "boundary_user.user", "id"), + ), + }, + }, + }) +} + +func TestAccRoleReadOrg(t *testing.T) { + tc := controller.NewTestController(t, tcConfig...) + defer tc.Shutdown() + url := tc.ApiAddrs()[0] + + dataSourceName := "data.boundary_role.role" + + var provider *schema.Provider + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories(&provider), + Steps: []resource.TestStep{ + { + Config: testConfig(url, fooOrg, roleReadOrg), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, IDKey), + resource.TestCheckResourceAttrSet(dataSourceName, ScopeIdKey), + resource.TestCheckResourceAttr(dataSourceName, NameKey, testRoleName), + resource.TestCheckResourceAttrSet(dataSourceName, DescriptionKey), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.#", roleGrantStringsKey), "1"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.0", roleGrantStringsKey), "ids=*;type=*;actions=read"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.#", roleGrantScopeIdsKey), "1"), + resource.TestCheckResourceAttrSet(dataSourceName, "scope.0.id"), + resource.TestCheckResourceAttr(dataSourceName, "scope.0.name", "org1"), + resource.TestCheckResourceAttr(dataSourceName, "scope.0.type", "org"), + resource.TestCheckResourceAttr(dataSourceName, fmt.Sprintf("%s.#", rolePrincipalIdsKey), "1"), + resource.TestCheckResourceAttrPair(dataSourceName, fmt.Sprintf("%s.0", rolePrincipalIdsKey), "boundary_user.user", "id"), + ), + }, + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6c724d29..74a8ed76 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -137,6 +137,7 @@ func New() *schema.Provider { "boundary_group": dataSourceGroup(), "boundary_scope": dataSourceScope(), "boundary_user": dataSourceUser(), + "boundary_role": dataSourceRole(), }, }