Add IAM binding and member support for organizations (#775)

This commit is contained in:
Vincent Roseberry 2017-11-21 14:30:48 -08:00 committed by GitHub
parent fdfa9a5eb0
commit e70d1fc44b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 345 additions and 0 deletions

View File

@ -0,0 +1,60 @@
package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)
var IamOrganizationSchema = map[string]*schema.Schema{
"org_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
}
type OrganizationIamUpdater struct {
resourceId string
Config *Config
}
func NewOrganizationIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) {
return &OrganizationIamUpdater{
resourceId: d.Get("org_id").(string),
Config: config,
}, nil
}
func (u *OrganizationIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
p, err := u.Config.clientResourceManager.Organizations.GetIamPolicy("organizations/"+u.resourceId, &cloudresourcemanager.GetIamPolicyRequest{}).Do()
if err != nil {
return nil, fmt.Errorf("Error retrieving IAM policy for %s: %s", u.DescribeResource(), err)
}
return p, nil
}
func (u *OrganizationIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
_, err := u.Config.clientResourceManager.Organizations.SetIamPolicy("organizations/"+u.resourceId, &cloudresourcemanager.SetIamPolicyRequest{
Policy: policy,
}).Do()
if err != nil {
return fmt.Errorf("Error setting IAM policy for %s: %s", u.DescribeResource(), err)
}
return nil
}
func (u *OrganizationIamUpdater) GetResourceId() string {
return u.resourceId
}
func (u *OrganizationIamUpdater) GetMutexKey() string {
return fmt.Sprintf("iam-organization-%s", u.resourceId)
}
func (u *OrganizationIamUpdater) DescribeResource() string {
return fmt.Sprintf("organization %q", u.resourceId)
}

View File

@ -129,7 +129,9 @@ func Provider() terraform.ResourceProvider {
"google_sql_database": resourceSqlDatabase(),
"google_sql_database_instance": resourceSqlDatabaseInstance(),
"google_sql_user": resourceSqlUser(),
"google_organization_iam_binding": ResourceIamBinding(IamOrganizationSchema, NewOrganizationIamUpdater),
"google_organization_iam_custom_role": resourceGoogleOrganizationIamCustomRole(),
"google_organization_iam_member": ResourceIamMember(IamOrganizationSchema, NewOrganizationIamUpdater),
"google_organization_policy": resourceGoogleOrganizationPolicy(),
"google_project": resourceGoogleProject(),
"google_project_iam_policy": resourceGoogleProjectIamPolicy(),

View File

@ -0,0 +1,186 @@
package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/cloudresourcemanager/v1"
"reflect"
"sort"
"testing"
)
// Bindings and members are tested serially to avoid concurrent updates of the org's IAM policy.
// When concurrent changes happen, the behavior is to abort and ask the user to retry allowing
// them to see the new diff instead of blindly overriding the policy stored in GCP. This desired
// behavior however induces flakiness in our acceptance tests, hence the need for running them
// serially.
func TestAccGoogleOrganizationIam(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
account := acctest.RandomWithPrefix("tf-test")
roleId := "tfIamTest" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// Test Iam Binding creation
Config: testAccGoogleOrganizationIamBinding_basic(account, roleId, org),
Check: testAccCheckGoogleOrganizationIamBindingExists("foo", "test-role", []string{
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
{
// Test Iam Binding update
Config: testAccGoogleOrganizationIamBinding_update(account, roleId, org),
Check: testAccCheckGoogleOrganizationIamBindingExists("foo", "test-role", []string{
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
{
// Test Iam Member creation (no update for member, no need to test)
Config: testAccGoogleOrganizationIamMember_basic(account, org),
Check: testAccCheckGoogleOrganizationIamMemberExists("foo", "roles/browser",
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
),
},
},
})
}
func testAccCheckGoogleOrganizationIamBindingExists(bindingResourceName, roleResourceName string, members []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
bindingRs, ok := s.RootModule().Resources["google_organization_iam_binding."+bindingResourceName]
if !ok {
return fmt.Errorf("Not found: %s", bindingResourceName)
}
roleRs, ok := s.RootModule().Resources["google_organization_iam_custom_role."+roleResourceName]
if !ok {
return fmt.Errorf("Not found: %s", roleResourceName)
}
config := testAccProvider.Meta().(*Config)
p, err := config.clientResourceManager.Organizations.GetIamPolicy("organizations/"+bindingRs.Primary.Attributes["org_id"], &cloudresourcemanager.GetIamPolicyRequest{}).Do()
if err != nil {
return err
}
for _, binding := range p.Bindings {
if binding.Role == roleRs.Primary.ID {
sort.Strings(members)
sort.Strings(binding.Members)
if reflect.DeepEqual(members, binding.Members) {
return nil
}
return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members)
}
}
return fmt.Errorf("No binding for role %q", roleRs.Primary.ID)
}
}
func testAccCheckGoogleOrganizationIamMemberExists(n, role, member string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["google_organization_iam_member."+n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
config := testAccProvider.Meta().(*Config)
p, err := config.clientResourceManager.Organizations.GetIamPolicy("organizations/"+rs.Primary.Attributes["org_id"], &cloudresourcemanager.GetIamPolicyRequest{}).Do()
if err != nil {
return err
}
for _, binding := range p.Bindings {
if binding.Role == role {
for _, m := range binding.Members {
if m == member {
return nil
}
}
return fmt.Errorf("Missing member %q, got %v", member, binding.Members)
}
}
return fmt.Errorf("No binding for role %q", role)
}
}
// We are using a custom role since iam_binding is authoritative on the member list and
// we want to avoid removing members from an existing role to prevent unwanted side effects.
func testAccGoogleOrganizationIamBinding_basic(account, role, org string) string {
return fmt.Sprintf(`
resource "google_service_account" "test-account" {
account_id = "%s"
display_name = "Iam Testing Account"
}
resource "google_organization_iam_custom_role" "test-role" {
role_id = "%s"
org_id = "%s"
title = "Iam Testing Role"
permissions = ["genomics.datasets.get"]
}
resource "google_organization_iam_binding" "foo" {
org_id = "%s"
role = "${google_organization_iam_custom_role.test-role.id}"
members = ["serviceAccount:${google_service_account.test-account.email}"]
}
`, account, role, org, org)
}
func testAccGoogleOrganizationIamBinding_update(account, role, org string) string {
return fmt.Sprintf(`
resource "google_service_account" "test-account" {
account_id = "%s"
display_name = "Iam Testing Account"
}
resource "google_organization_iam_custom_role" "test-role" {
role_id = "%s"
org_id = "%s"
title = "Iam Testing Role"
permissions = ["genomics.datasets.get"]
}
resource "google_service_account" "test-account-2" {
account_id = "%s-2"
display_name = "Iam Testing Account"
}
resource "google_organization_iam_binding" "foo" {
org_id = "%s"
role = "${google_organization_iam_custom_role.test-role.id}"
members = [
"serviceAccount:${google_service_account.test-account.email}",
"serviceAccount:${google_service_account.test-account-2.email}"
]
}
`, account, role, org, account, org)
}
func testAccGoogleOrganizationIamMember_basic(account, org string) string {
return fmt.Sprintf(`
resource "google_service_account" "test-account" {
account_id = "%s"
display_name = "Iam Testing Account"
}
resource "google_organization_iam_member" "foo" {
org_id = "%s"
role = "roles/browser"
member = "serviceAccount:${google_service_account.test-account.email}"
}
`, account, org)
}

View File

@ -0,0 +1,48 @@
---
layout: "google"
page_title: "Google: google_organization_iam_binding"
sidebar_current: "docs-google-organization-iam-binding"
description: |-
Allows management of a single binding with an IAM policy for a Google Cloud Platform Organization.
---
# google\_organization\_iam\_binding
Allows creation and management of a single binding within IAM policy for
an existing Google Cloud Platform Organization.
~> **Note:** This resource __must not__ be used in conjunction with
`google_organization_iam_member` for the __same role__ or they will fight over
what your policy should be.
## Example Usage
```hcl
resource "google_organization_iam_binding" "binding" {
org_id = "123456789"
role = "roles/browser"
members = [
"user:jane@example.com",
]
}
```
## Argument Reference
The following arguments are supported:
* `org_id` - (Required) The numeric ID of the organization in which you want to create a custom role.
* `role` - (Required) The role that should be applied. Only one
`google_organization_iam_binding` can be used per role.
* `members` - (Required) A list of users that the role should apply to.
## Attributes Reference
In addition to the arguments listed above, the following computed attributes are
exported:
* `etag` - (Computed) The etag of the organization's IAM policy.

View File

@ -0,0 +1,43 @@
---
layout: "google"
page_title: "Google: google_organization_iam_member"
sidebar_current: "docs-google-organization-iam-member"
description: |-
Allows management of a single member for a single binding on the IAM policy for a Google Cloud Platform Organization.
---
# google\_organization\_iam\_member
Allows creation and management of a single member for a single binding within
the IAM policy for an existing Google Cloud Platform Organization.
~> **Note:** This resource __must not__ be used in conjunction with
`google_organization_iam_binding` for the __same role__ or they will fight over
what your policy should be.
## Example Usage
```hcl
resource "google_organization_iam_member" "binding" {
org_id = "0123456789"
role = "roles/editor"
member = "user:jane@example.com"
}
```
## Argument Reference
The following arguments are supported:
* `org_id` - (Required) The numeric ID of the organization in which you want to create a custom role.
* `role` - (Required) The role that should be applied.
* `member` - (Required) The user that the role should apply to.
## Attributes Reference
In addition to the arguments listed above, the following computed attributes are
exported:
* `etag` - (Computed) The etag of the organization's IAM policy.

View File

@ -95,9 +95,15 @@
<li<%= sidebar_current("docs-google-organization-policy") %>>
<a href="/docs/providers/google/r/google_organization_policy.html">google_organization_policy</a>
</li>
<li<%= sidebar_current("docs-google-organization-iam-binding") %>>
<a href="/docs/providers/google/r/google_organization_iam_binding.html">google_organization_iam_binding</a>
</li>
<li<%= sidebar_current("docs-google-organization-iam-custom-role") %>>
<a href="/docs/providers/google/r/google_organization_iam_custom_role.html">google_organization_iam_custom_role</a>
</li>
<li<%= sidebar_current("docs-google-organization-iam-member") %>>
<a href="/docs/providers/google/r/google_organization_iam_member.html">google_organization_iam_member</a>
</li>
<li<%= sidebar_current("docs-google-project-x") %>>
<a href="/docs/providers/google/r/google_project.html">google_project</a>
</li>