Repository: GoogleCloudPlatform/magic-modules
Pull request #13304: Add data source for retrieving organization iam custom roles
Author: login: bestefreund
Created: 2025-03-10T07:58:38Z | Merged: 2025-03-24T21:32:15Z

<!--
Complete the self-review checklist to help speed up the review process: https://googlecloudplatform.github.io/magic-modules/contribute/review-pr/

If your PR is still work in progress, please create it in draft mode.

Put a description of what this PR is for here, along with any references to issues that this resolves or contributes to.
For example: Fixes https://github.com/hashicorp/terraform-provider-google/issues/ISSUE_ID
-->

Adds a new data source "google_organization_iam_custom_roles", allowing to retrieve organization iam custom roles.

Fixes: https://github.com/hashicorp/terraform-provider-google/issues/21779

**Release Note Template for Downstream PRs (will be copied)**

```release-note:new-datasource
`google_organization_iam_custom_roles`
```


Diff:
diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl
index 8e06e6f7c2e0..017875f9c80f 100644
--- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl
+++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl
@@ -187,6 +187,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
 	"google_organization":                              resourcemanager.DataSourceGoogleOrganization(),
 	"google_organizations":                             resourcemanager.DataSourceGoogleOrganizations(),
 	"google_organization_iam_custom_role":              resourcemanager.DataSourceGoogleOrganizationIamCustomRole(),
+	"google_organization_iam_custom_roles":             resourcemanager.DataSourceGoogleOrganizationIamCustomRoles(),
 	{{- if ne $.TargetVersionName "ga" }}
 	"google_parameter_manager_parameter":               parametermanager.DataSourceParameterManagerParameter(),
 	"google_parameter_manager_parameters":              parametermanager.DataSourceParameterManagerParameters(),
diff --git a/mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles.go b/mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles.go
new file mode 100644
index 000000000000..e3b7d49f0420
--- /dev/null
+++ b/mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles.go
@@ -0,0 +1,143 @@
+package resourcemanager
+
+import (
+	"context"
+	"fmt"
+	"path"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-provider-google/google/tpgresource"
+	transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
+	"google.golang.org/api/iam/v1"
+)
+
+func DataSourceGoogleOrganizationIamCustomRoles() *schema.Resource {
+	return &schema.Resource{
+		Read: dataSourceOrganizationIamCustomRolesRead,
+		Schema: map[string]*schema.Schema{
+			"org_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"show_deleted": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  false,
+			},
+			"view": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Default:      "BASIC",
+				ValidateFunc: validateViewOrganizationIamCustomRoles,
+			},
+			"roles": {
+				Type:     schema.TypeList,
+				Computed: true,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"deleted": {
+							Type:     schema.TypeBool,
+							Computed: true,
+						},
+						"description": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"id": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"name": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"permissions": {
+							Type:     schema.TypeList,
+							Computed: true,
+							Elem:     &schema.Schema{Type: schema.TypeString},
+						},
+						"role_id": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"stage": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+						"title": {
+							Type:     schema.TypeString,
+							Computed: true,
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func validateViewOrganizationIamCustomRoles(val interface{}, key string) ([]string, []error) {
+	v := val.(string)
+	var errs []error
+
+	if v != "BASIC" && v != "FULL" {
+		errs = append(errs, fmt.Errorf("%q must be either 'BASIC' or 'FULL', got %q", key, v))
+	}
+
+	return nil, errs
+}
+
+func dataSourceOrganizationIamCustomRolesRead(d *schema.ResourceData, meta interface{}) error {
+	config := meta.(*transport_tpg.Config)
+	userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
+	if err != nil {
+		return err
+	}
+
+	orgId := d.Get("org_id").(string)
+
+	roles := make([]map[string]interface{}, 0)
+
+	showDeleted := d.Get("show_deleted").(bool)
+	view := d.Get("view").(string)
+
+	request := config.NewIamClient(userAgent).Organizations.Roles.List("organizations/" + orgId).ShowDeleted(showDeleted).View(view)
+
+	err = request.Pages(context.Background(), func(roleList *iam.ListRolesResponse) error {
+		for _, role := range roleList.Roles {
+			var permissions []string
+
+			switch view {
+			case "BASIC":
+				permissions = []string{}
+			case "FULL":
+				permissions = role.IncludedPermissions
+			default:
+				return fmt.Errorf("Unsupported view type: %s", view)
+			}
+
+			roles = append(roles, map[string]interface{}{
+				"deleted":     role.Deleted,
+				"description": role.Description,
+				"id":          role.Name,
+				"name":        role.Name,
+				"permissions": permissions,
+				"role_id":     path.Base(role.Name),
+				"stage":       role.Stage,
+				"title":       role.Title,
+			})
+		}
+		return nil
+	})
+
+	if err != nil {
+		return fmt.Errorf("Error retrieving organization custom roles: %s", err)
+	}
+
+	if err := d.Set("roles", roles); err != nil {
+		return fmt.Errorf("Error setting organization custom roles: %s", err)
+	}
+
+	d.SetId("organizations/" + orgId)
+
+	return nil
+}
diff --git a/mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles_test.go b/mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles_test.go
new file mode 100644
index 000000000000..4889291fe260
--- /dev/null
+++ b/mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles_test.go
@@ -0,0 +1,55 @@
+package resourcemanager_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-provider-google/google/acctest"
+	"github.com/hashicorp/terraform-provider-google/google/envvar"
+)
+
+func TestAccDataSourceGoogleOrganizationIamCustomRoles_basic(t *testing.T) {
+	t.Parallel()
+
+	orgId := envvar.GetTestOrgFromEnv(t)
+	roleId := "tfIamCustomRole" + acctest.RandString(t, 10)
+
+	acctest.VcrTest(t, resource.TestCase{
+		PreCheck:                 func() { acctest.AccTestPreCheck(t) },
+		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCheckGoogleOrganizationIamCustomRolesConfig(orgId, roleId),
+				Check: resource.ComposeTestCheckFunc(
+					// We can't guarantee no organization won't have our custom role as first element, so we'll check set-ness rather than correctness
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.id"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.name"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.role_id"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.stage"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.title"),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckGoogleOrganizationIamCustomRolesConfig(orgId string, roleId string) string {
+	return fmt.Sprintf(`
+resource "google_organization_iam_custom_role" "this" {
+  org_id  = "%s"
+  role_id = "%s"
+  title   = "Terraform Test"
+
+  permissions = [
+	"iam.roles.create",
+	"iam.roles.delete",
+	"iam.roles.list",
+  ]
+}
+
+data "google_organization_iam_custom_roles" "this" {
+  org_id = google_organization_iam_custom_role.this.org_id
+}
+`, orgId, roleId)
+}
diff --git a/mmv1/third_party/terraform/website/docs/d/organization_iam_custom_roles.html.markdown b/mmv1/third_party/terraform/website/docs/d/organization_iam_custom_roles.html.markdown
new file mode 100644
index 000000000000..2125e404b2f0
--- /dev/null
+++ b/mmv1/third_party/terraform/website/docs/d/organization_iam_custom_roles.html.markdown
@@ -0,0 +1,54 @@
+---
+subcategory: "Cloud Platform"
+description: |-
+Get information about a Google Cloud Organization IAM Custom Roles.
+---
+
+# google_organization_iam_custom_roles
+
+Get information about a Google Cloud Organization IAM Custom Roles.
+Note that you must have the `roles/iam.organizationRoleViewer`.
+See [the official documentation](https://cloud.google.com/iam/docs/creating-custom-roles)
+and [API](https://cloud.google.com/iam/docs/reference/rest/v1/organizations.roles/list).
+
+```hcl
+data "google_organization_iam_custom_roles" "example" {
+  org_id       = "1234567890"
+  show_deleted = true
+  view         = "FULL"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `org_id` - (Required) The numeric ID of the organization.
+
+* `show_deleted` - (Optional) Include Roles that have been deleted. Defaults to `false`.
+
+* `view` - (Optional) When `"FULL"` is specified, the `permissions` field is returned, which includes a list of all permissions in the role. The default value is `"BASIC"`, which does not return the `permissions`.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `roles` - A list of all retrieved custom roles roles. Structure is [defined below](#nested_roles).
+
+<a name="nested_roles"></a>The `roles` block supports:
+
+* `deleted` - The current deleted state of the role.
+
+* `description` - A human-readable description for the role.
+
+* `id` - an identifier for the resource with the format `organizations/{{org_id}}/roles/{{role_id}}`.
+
+* `name` - The name of the role in the format `organizations/{{org_id}}/roles/{{role_id}}`. Like `id`, this field can be used as a reference in other resources such as IAM role bindings.
+
+* `permissions` -  The names of the permissions this role grants when bound in an IAM policy.
+
+* `role_id` - The camel case role id used for this role.
+
+* `stage` - The current launch stage of the role. List of possible stages is [here](https://cloud.google.com/iam/reference/rest/v1/organizations.roles#Role.RoleLaunchStage).
+
+* `title` - A human-readable title for the role.


Reviews:
user: login: roaks3
body: Overall LGTM, but had a question about the `view` field
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T21:43:32Z
user: login: bestefreund
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T06:57:47Z
user: login: roaks3
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T17:04:16Z
user: login: roaks3
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:38:13Z
user: login: roaks3
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T21:15:46Z

Review comments:
diff_hunk: @@ -0,0 +1,143 @@
+package resourcemanager
+
+import (
+	"context"
+	"fmt"
+	"path"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-provider-google/google/tpgresource"
+	transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
+	"google.golang.org/api/iam/v1"
+)
+
+func DataSourceGoogleOrganizationIamCustomRoles() *schema.Resource {
+	return &schema.Resource{
+		Read: dataSourceOrganizationIamCustomRolesRead,
+		Schema: map[string]*schema.Schema{
+			"org_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"show_deleted": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  false,
+			},
+			"view": {
path: mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles.go
user: login: roaks3
body: I understand the basic idea of this field, but I'm curious why you feel it is needed. It looks like the permissions are being returned by the API anyway, so I wouldn't expect a performance reason. Wouldn't a user be able to simply not use the permissions field if they don't need it?
created_at: 2025-03-19T21:39:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+package resourcemanager_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-provider-google/google/acctest"
+	"github.com/hashicorp/terraform-provider-google/google/envvar"
+)
+
+func TestAccDataSourceGoogleOrganizationIamCustomRoles_basic(t *testing.T) {
+	t.Parallel()
+
+	orgId := envvar.GetTestOrgFromEnv(t)
+	roleId := "tfIamCustomRole" + acctest.RandString(t, 10)
+
+	acctest.VcrTest(t, resource.TestCase{
+		PreCheck:                 func() { acctest.AccTestPreCheck(t) },
+		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCheckGoogleOrganizationIamCustomRolesConfig(orgId, roleId),
+				Check: resource.ComposeTestCheckFunc(
+					// We can't guarantee no organization won't have our custom role as first element, so we'll check set-ness rather than correctness
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.id"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.name"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.role_id"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.stage"),
+					resource.TestCheckResourceAttrSet("data.google_organization_iam_custom_roles.this", "roles.0.title"),
+				),
+			},
+		},
+	})
+}
+
+func testAccCheckGoogleOrganizationIamCustomRolesConfig(orgId string, roleId string) string {
+	return fmt.Sprintf(`
+locals {
path: mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles_test.go
user: login: roaks3
body: Could you remove this `locals` block and instead specify the values directly on the resource?
created_at: 2025-03-19T21:43:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,143 @@
+package resourcemanager
+
+import (
+	"context"
+	"fmt"
+	"path"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-provider-google/google/tpgresource"
+	transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
+	"google.golang.org/api/iam/v1"
+)
+
+func DataSourceGoogleOrganizationIamCustomRoles() *schema.Resource {
+	return &schema.Resource{
+		Read: dataSourceOrganizationIamCustomRolesRead,
+		Schema: map[string]*schema.Schema{
+			"org_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"show_deleted": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  false,
+			},
+			"view": {
path: mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles.go
user: login: bestefreund
body: Thank you for the input!

You are completely right!

My intention behind this field is to keep the functionality from the IAM-API.

The `view` field is an adoption of the provided `view` from the [API's query paramters](https://cloud.google.com/iam/docs/reference/rest/v1/organizations.roles/list#query-parameters).

Additionally, the use-case why I started to contribute this data source was that some of my requestors just wanted to get basic information about custom roles like `role_id` and `name` just for further usage in IAM-bindings.

In addition to that, some of our custom roles include tons of permissions that would also be saved in the state without being used.

All in all the `view` field from the API does exactly what I need especially in my case.

I've also written the corresponding data source for project IAM roles were the `view` field has been accepted and I'd rather keep it.



created_at: 2025-03-20T06:57:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,143 @@
+package resourcemanager
+
+import (
+	"context"
+	"fmt"
+	"path"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-provider-google/google/tpgresource"
+	transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
+	"google.golang.org/api/iam/v1"
+)
+
+func DataSourceGoogleOrganizationIamCustomRoles() *schema.Resource {
+	return &schema.Resource{
+		Read: dataSourceOrganizationIamCustomRolesRead,
+		Schema: map[string]*schema.Schema{
+			"org_id": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+			"show_deleted": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  false,
+			},
+			"view": {
path: mmv1/third_party/terraform/services/resourcemanager/data_source_google_organization_iam_custom_roles.go
user: login: roaks3
body: Alright, makes sense, thanks for explaining!
created_at: 2025-03-21T17:04:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-10T07:59:37Z
body: Hello! I am a robot. Tests will require approval from a repository maintainer to run. Googlers: see go/terraform-auto-test-runs to set up automatic test runs.

@roaks3, a repository maintainer, has been assigned to [review your changes](https://googlecloudplatform.github.io/magic-modules/contribute/review-pr/). If you have not received review feedback within 2 business days, please leave a comment on this PR asking them to take a look.

You can help make sure that review is quick by [doing a self-review](https://googlecloudplatform.github.io/magic-modules/contribute/review-pr/) and by [running impacted tests locally](https://googlecloudplatform.github.io/magic-modules/get-started/run-provider-tests/).

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-13T09:42:20Z
body: @roaks3 This PR has been waiting for review for 3 weekdays. Please take a look! Use the label `disable-review-reminders` to disable these notifications.
author_association: NONE
user: login: bestefreund
created_at: 2025-03-17T07:54:59Z
body: Hi @roaks3, 

please, take look on this and let me know, if here is anything further I can contribute
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-17T09:42:34Z
body: @GoogleCloudPlatform/terraform-team @roaks3 This PR has been waiting for review for 1 week. Please take a look! Use the label `disable-review-reminders` to disable these notifications.
author_association: NONE
user: login: bestefreund
created_at: 2025-03-20T07:07:29Z
body: I've rebased this feature branch on the `main` branch to resolve the merge conflict with `mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl`
author_association: CONTRIBUTOR
user: login: modular-magician
created_at: 2025-03-21T17:15:16Z
body: Hi there, I'm the Modular magician. I've detected the following information about your changes:

## Diff report

Your PR generated some diffs in downstreams - here they are.

`google` provider: [Diff](https://github.com/modular-magician/terraform-provider-google/compare/auto-pr-13304-old..auto-pr-13304) ( 4 files changed, 257 insertions(+))
`google-beta` provider: [Diff](https://github.com/modular-magician/terraform-provider-google-beta/compare/auto-pr-13304-old..auto-pr-13304) ( 4 files changed, 257 insertions(+))





author_association: COLLABORATOR
user: login: modular-magician
created_at: 2025-03-21T17:25:25Z
body: #### Tests analytics
Total tests: 165
Passed tests: 133
Skipped tests: 30
Affected tests: 2

<details>
<summary>Click here to see the affected service packages</summary>
<blockquote>

<ul>
<li>resourcemanager</li>

</ul>

</blockquote>
</details>

#### Action taken
<details>
<summary>Found 2 affected test(s) by replaying old test recordings. Starting RECORDING based on the most recent commit. Click here to see the affected tests
</summary>
<blockquote>
<ul>
<li>TestAccDataSourceGoogleOrganizationIamCustomRoles_basic</li>
<li>TestAccEphemeralServiceAccountKey_basic</li>

</ul>
</blockquote>
</details>

[Get to know how VCR tests work](https://googlecloudplatform.github.io/magic-modules/develop/test/test/)
author_association: COLLABORATOR
user: login: modular-magician
created_at: 2025-03-21T17:26:07Z
body: 🟢 Tests passed during RECORDING mode:
`TestAccDataSourceGoogleOrganizationIamCustomRoles_basic` [[Debug log](https://storage.cloud.google.com/ci-vcr-logs/beta/refs/heads/auto-pr-13304/artifacts/b7e229e8-29af-4033-8f7d-b75c398d247a/recording/TestAccDataSourceGoogleOrganizationIamCustomRoles_basic.log)]

🟢 No issues found for passed tests after REPLAYING rerun.

---


🔴 Tests failed during RECORDING mode:
`TestAccEphemeralServiceAccountKey_basic` [[Error message](https://storage.cloud.google.com/ci-vcr-logs/beta/refs/heads/auto-pr-13304/artifacts/b7e229e8-29af-4033-8f7d-b75c398d247a/build-log/recording_build/TestAccEphemeralServiceAccountKey_basic_recording_test.log)] [[Debug log](https://storage.cloud.google.com/ci-vcr-logs/beta/refs/heads/auto-pr-13304/artifacts/b7e229e8-29af-4033-8f7d-b75c398d247a/recording/TestAccEphemeralServiceAccountKey_basic.log)]



🔴 Errors occurred during RECORDING mode. Please fix them to complete your PR.



View the [build log](https://storage.cloud.google.com/ci-vcr-logs/beta/refs/heads/auto-pr-13304/artifacts/b7e229e8-29af-4033-8f7d-b75c398d247a/build-log/recording_test.log) or the [debug log](https://console.cloud.google.com/storage/browser/ci-vcr-logs/beta/refs/heads/auto-pr-13304/artifacts/b7e229e8-29af-4033-8f7d-b75c398d247a/recording) for each test
author_association: COLLABORATOR
user: login: bestefreund
created_at: 2025-03-24T15:09:59Z
body: Hi @roaks3,

is there anything more I can contribute to get this merged?
author_association: CONTRIBUTOR
user: login: roaks3
created_at: 2025-03-24T21:15:37Z
body: Ugh sorry, I just forgot to click the button. Thanks for the ping!
author_association: CONTRIBUTOR