Skip to content

Commit

Permalink
Data source for IAM Testable Permissions (GoogleCloudPlatform#3460)
Browse files Browse the repository at this point in the history
* Added new data source for iam_testable_permissions

* Added tests and docs

* Fixed linter errors

* Use sdk validation package and allow case-insensitive

* Changed stage to stages list and concat results
  • Loading branch information
onetwopunch authored and Nathan Klish committed May 18, 2020
1 parent d474f31 commit 54dab10
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package google

import (
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func dataSourceGoogleIamTestablePermissions() *schema.Resource {
return &schema.Resource{
Read: dataSourceGoogleIamTestablePermissionsRead,
Schema: map[string]*schema.Schema{
"full_resource_name": {
Type: schema.TypeString,
Required: true,
},
"stages": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{"ALPHA", "BETA", "GA", "DEPRECATED"}, true),
},
},
"custom_support_level": {
Type: schema.TypeString,
Optional: true,
Default: "SUPPORTED",
ValidateFunc: validation.StringInSlice([]string{"NOT_SUPPORTED", "SUPPORTED", "TESTING"}, true),
},
"permissions": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Computed: true,
},
"title": {
Type: schema.TypeString,
Computed: true,
},
"custom_support_level": {
Type: schema.TypeString,
Computed: true,
},
"stage": {
Type: schema.TypeString,
Computed: true,
},
"api_disabled": {
Type: schema.TypeBool,
Computed: true,
},
},
},
},
},
}
}

func dataSourceGoogleIamTestablePermissionsRead(d *schema.ResourceData, meta interface{}) (err error) {
config := meta.(*Config)
body := make(map[string]interface{})
body["pageSize"] = 500
permissions := make([]map[string]interface{}, 0)

custom_support_level := strings.ToUpper(d.Get("custom_support_level").(string))
stages := []string{}
for _, e := range d.Get("stages").([]interface{}) {
stages = append(stages, strings.ToUpper(e.(string)))
}
if len(stages) == 0 {
// Since schema.TypeLists cannot specify defaults, we'll specify it here
stages = append(stages, "GA")
}
for {
url := "https://iam.googleapis.com/v1/permissions:queryTestablePermissions"
body["fullResourceName"] = d.Get("full_resource_name").(string)
res, err := sendRequest(config, "POST", "", url, body)
if err != nil {
return fmt.Errorf("Error retrieving permissions: %s", err)
}

pagePermissions := flattenTestablePermissionsList(res["permissions"], custom_support_level, stages)
permissions = append(permissions, pagePermissions...)
pToken, ok := res["nextPageToken"]
if ok && pToken != nil && pToken.(string) != "" {
body["pageToken"] = pToken.(string)
} else {
break
}
}

if err := d.Set("permissions", permissions); err != nil {
return fmt.Errorf("Error retrieving permissions: %s", err)
}

d.SetId(d.Get("full_resource_name").(string))
return nil
}

func flattenTestablePermissionsList(v interface{}, custom_support_level string, stages []string) []map[string]interface{} {
if v == nil {
return make([]map[string]interface{}, 0)
}

ls := v.([]interface{})
permissions := make([]map[string]interface{}, 0, len(ls))
for _, raw := range ls {
p := raw.(map[string]interface{})

if _, ok := p["name"]; ok {
var csl bool
if custom_support_level == "SUPPORTED" {
csl = p["customRolesSupportLevel"] == nil || p["customRolesSupportLevel"] == "SUPPORTED"
} else {
csl = p["customRolesSupportLevel"] == custom_support_level
}
if csl && p["stage"] != nil && stringInSlice(stages, p["stage"].(string)) {
permissions = append(permissions, map[string]interface{}{
"name": p["name"],
"title": p["title"],
"stage": p["stage"],
"api_disabled": p["apiDisabled"],
"custom_support_level": p["customRolesSupportLevel"],
})
}
}
}

return permissions
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package google

import (
"fmt"
"strconv"
"testing"

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

func TestAccDataSourceGoogleIamTestablePermissions_basic(t *testing.T) {
t.Parallel()

project := getTestProjectFromEnv()
vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
data "google_iam_testable_permissions" "perms" {
full_resource_name = "//cloudresourcemanager.googleapis.com/projects/%s"
}
`, project),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleIamTestablePermissionsMeta(
project,
"data.google_iam_testable_permissions.perms",
[]string{"GA"},
"",
),
),
},
{
Config: fmt.Sprintf(`
data "google_iam_testable_permissions" "perms" {
full_resource_name = "//cloudresourcemanager.googleapis.com/projects/%s"
stages = ["GA"]
}
`, project),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleIamTestablePermissionsMeta(
project,
"data.google_iam_testable_permissions.perms",
[]string{"GA"},
"",
),
),
},
{
Config: fmt.Sprintf(`
data "google_iam_testable_permissions" "perms" {
full_resource_name = "//cloudresourcemanager.googleapis.com/projects/%s"
custom_support_level = "NOT_SUPPORTED"
stages = ["BETA"]
}
`, project),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleIamTestablePermissionsMeta(
project,
"data.google_iam_testable_permissions.perms",
[]string{"BETA"},
"NOT_SUPPORTED",
),
),
},
{
Config: fmt.Sprintf(`
data "google_iam_testable_permissions" "perms" {
full_resource_name = "//cloudresourcemanager.googleapis.com/projects/%s"
custom_support_level = "not_supported"
stages = ["beta"]
}
`, project),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleIamTestablePermissionsMeta(
project,
"data.google_iam_testable_permissions.perms",
[]string{"BETA"},
"NOT_SUPPORTED",
),
),
},
{
Config: fmt.Sprintf(`
data "google_iam_testable_permissions" "perms" {
full_resource_name = "//cloudresourcemanager.googleapis.com/projects/%s"
stages = ["ga", "beta"]
}
`, project),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleIamTestablePermissionsMeta(
project,
"data.google_iam_testable_permissions.perms",
[]string{"GA", "BETA"},
"",
),
),
},
},
})
}

func testAccCheckGoogleIamTestablePermissionsMeta(project string, n string, expectedStages []string, expectedSupportLevel string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find perms data source: %s", n)
}
expectedId := fmt.Sprintf("//cloudresourcemanager.googleapis.com/projects/%s", project)
if rs.Primary.ID != expectedId {
return fmt.Errorf("perms data source ID not set.")
}
attrs := rs.Primary.Attributes
count, ok := attrs["permissions.#"]
if !ok {
return fmt.Errorf("can't find 'permsissions' attribute")
}
permCount, err := strconv.Atoi(count)
if err != nil {
return err
}
if permCount < 2 {
return fmt.Errorf("count should be greater than 2")
}
foundStageCounter := len(expectedStages)
foundSupport := false

for i := 0; i < permCount; i++ {
for s := 0; s < len(expectedStages); s++ {
stageKey := "permissions." + strconv.Itoa(i) + ".stage"
supportKey := "permissions." + strconv.Itoa(i) + ".custom_support_level"
if stringInSlice(expectedStages, attrs[stageKey]) {
foundStageCounter -= 1
}
if attrs[supportKey] == expectedSupportLevel {
foundSupport = true
}
if foundSupport && foundStageCounter == 0 {
return nil
}
}
}

if foundSupport { // This means we didn't find a stage
return fmt.Errorf("Could not find stages %v in output", expectedStages)
}
if foundStageCounter == 0 { // This meads we didn't fins a custom_support_level
return fmt.Errorf("Could not find custom_support_level %s in output", expectedSupportLevel)
}
return fmt.Errorf("Unable to find customSupportLevel or stages in output")
}
}
1 change: 1 addition & 0 deletions third_party/terraform/utils/provider.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func Provider() terraform.ResourceProvider {
"google_dns_managed_zone": dataSourceDnsManagedZone(),
"google_iam_policy": dataSourceGoogleIamPolicy(),
"google_iam_role": dataSourceGoogleIamRole(),
"google_iam_testable_permissions": dataSourceGoogleIamTestablePermissions(),
"google_kms_crypto_key": dataSourceGoogleKmsCryptoKey(),
"google_kms_crypto_key_version": dataSourceGoogleKmsCryptoKeyVersion(),
"google_kms_key_ring": dataSourceGoogleKmsKeyRing(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
subcategory: "Cloud Platform"
layout: "google"
page_title: "Google: google_projects"
sidebar_current: "docs-google-datasource-iam-testable-permissions"
description: |-
Retrieve a list of testable permissions for a resource. Testable permissions mean the permissions that user can add or remove in a role at a given resource. The resource can be referenced either via the full resource name or via a URI.
---

# google\_iam\_testable\_permissions

Retrieve a list of testable permissions for a resource. Testable permissions mean the permissions that user can add or remove in a role at a given resource. The resource can be referenced either via the full resource name or via a URI.

## Example Usage - searching for projects about to be deleted in an org

```hcl
data "google_iam_testable_permissions" "perms" {
full_resource_name = "//cloudresourcemanager.googleapis.com/projects/my-project"
stages = ["GA", "BETA"]
}
```

## Argument Reference

The following arguments are supported:

* `full_resource_name` - (Required) See [full resource name documentation](https://cloud.google.com/apis/design/resource_names#full_resource_name) for more detail.
* `stages` - (Optional) The acceptable release stages of the permission in the output. Note that `BETA` does not include permissions in `GA`, but you can specify both with `["GA", "BETA"]` for example. Can be a list of `"ALPHA"`, `"BETA"`, `"GA"`, `"DEPRECATED"`. Default is `["GA"]`.
* `custom_support_level` - (Optional) The level of support for custom roles. Can be one of `"NOT_SUPPORTED"`, `"SUPPORTED"`, `"TESTING"`. Default is `"SUPPORTED"`

## Attributes Reference

The following attributes are exported:

* `permissions` - A list of permissions matching the provided input. Structure is defined below.

The `permissions` block supports:

* `name` - Name of the permission.
* `title` - Human readable title of the permission.
* `stage` - Release stage of the permission.
* `custom_support_level` - The the support level of this permission for custom roles.
* `api_disabled` - Whether the corresponding API has been enabled for the resource.

0 comments on commit 54dab10

Please sign in to comment.