Skip to content

Commit

Permalink
Add billing account IAM (GoogleCloudPlatform#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
modular-magician authored and rileykarson committed Nov 19, 2018
1 parent 86ba1ad commit b8895d9
Show file tree
Hide file tree
Showing 7 changed files with 455 additions and 0 deletions.
105 changes: 105 additions & 0 deletions google-beta/iam_billing_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package google

import (
"fmt"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudresourcemanager/v1"
)

var IamBillingAccountSchema = map[string]*schema.Schema{
"billing_account_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
}

type BillingAccountIamUpdater struct {
billingAccountId string
Config *Config
}

func NewBillingAccountIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) {
return &BillingAccountIamUpdater{
billingAccountId: canonicalBillingAccountId(d.Get("billing_account_id").(string)),
Config: config,
}, nil
}

func BillingAccountIdParseFunc(d *schema.ResourceData, _ *Config) error {
d.Set("billing_account_id", d.Id())
return nil
}

func (u *BillingAccountIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
return getBillingAccountIamPolicyByBillingAccountName(u.billingAccountId, u.Config)
}

func (u *BillingAccountIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
billingPolicy, err := resourceManagerToBillingPolicy(policy)
if err != nil {
return err
}

_, err = u.Config.clientBilling.BillingAccounts.SetIamPolicy("billingAccounts/"+u.billingAccountId, &cloudbilling.SetIamPolicyRequest{
Policy: billingPolicy,
}).Do()

if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

return nil
}

func (u *BillingAccountIamUpdater) GetResourceId() string {
return u.billingAccountId
}

func (u *BillingAccountIamUpdater) GetMutexKey() string {
return fmt.Sprintf("iam-billing-account-%s", u.billingAccountId)
}

func (u *BillingAccountIamUpdater) DescribeResource() string {
return fmt.Sprintf("billingAccount %q", u.billingAccountId)
}

func canonicalBillingAccountId(resource string) string {
return resource
}

func resourceManagerToBillingPolicy(p *cloudresourcemanager.Policy) (*cloudbilling.Policy, error) {
out := &cloudbilling.Policy{}
err := Convert(p, out)
if err != nil {
return nil, errwrap.Wrapf("Cannot convert a v1 policy to a billing policy: {{err}}", err)
}
return out, nil
}

func billingToResourceManagerPolicy(p *cloudbilling.Policy) (*cloudresourcemanager.Policy, error) {
out := &cloudresourcemanager.Policy{}
err := Convert(p, out)
if err != nil {
return nil, errwrap.Wrapf("Cannot convert a billing policy to a v1 policy: {{err}}", err)
}
return out, nil
}

// Retrieve the existing IAM Policy for a billing account
func getBillingAccountIamPolicyByBillingAccountName(resource string, config *Config) (*cloudresourcemanager.Policy, error) {
p, err := config.clientBilling.BillingAccounts.GetIamPolicy("billingAccounts/" + resource).Do()

if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for billing account %q: {{err}}", resource), err)
}

v1Policy, err := billingToResourceManagerPolicy(p)
if err != nil {
return nil, err
}

return v1Policy, nil
}
3 changes: 3 additions & 0 deletions google-beta/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ func Provider() terraform.ResourceProvider {
"google_bigquery_table": resourceBigQueryTable(),
"google_bigtable_instance": resourceBigtableInstance(),
"google_bigtable_table": resourceBigtableTable(),
"google_billing_account_iam_binding": ResourceIamBindingWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc),
"google_billing_account_iam_member": ResourceIamMemberWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc),
"google_billing_account_iam_policy": ResourceIamPolicyWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc),
"google_cloudbuild_trigger": resourceCloudBuildTrigger(),
"google_cloudfunctions_function": resourceCloudFunctionsFunction(),
"google_cloudiot_registry": resourceCloudIoTRegistry(),
Expand Down
178 changes: 178 additions & 0 deletions google-beta/resource_google_billing_account_iam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package google

import (
"fmt"
"reflect"
"sort"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

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

billing := getTestBillingAccountFromEnv(t)
account := acctest.RandomWithPrefix("tf-test")
role := "roles/billing.viewer"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// Test Iam Binding creation
Config: testAccBillingAccountIamBinding_basic(account, billing, role),
Check: testAccCheckGoogleBillingAccountIamBindingExists("foo", role, []string{
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
{
ResourceName: "google_billing_account_iam_binding.foo",
ImportStateId: fmt.Sprintf("%s roles/billing.viewer", billing),
ImportState: true,
ImportStateVerify: true,
},
{
// Test Iam Binding update
Config: testAccBillingAccountIamBinding_update(account, billing, role),
Check: testAccCheckGoogleBillingAccountIamBindingExists("foo", role, []string{
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
}),
},
{
ResourceName: "google_billing_account_iam_binding.foo",
ImportStateId: fmt.Sprintf("%s roles/billing.viewer", billing),
ImportState: true,
ImportStateVerify: true,
},
{
// Test Iam Member creation (no update for member, no need to test)
Config: testAccBillingAccountIamMember_basic(account, billing, role),
Check: testAccCheckGoogleBillingAccountIamMemberExists("foo", "roles/billing.viewer",
fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()),
),
},
{
ResourceName: "google_billing_account_iam_member.foo",
ImportStateId: fmt.Sprintf("%s roles/billing.viewer serviceAccount:%s@%s.iam.gserviceaccount.com", billing, account, getTestProjectFromEnv()),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckGoogleBillingAccountIamBindingExists(bindingResourceName, role string, members []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
bindingRs, ok := s.RootModule().Resources["google_billing_account_iam_binding."+bindingResourceName]
if !ok {
return fmt.Errorf("Not found: %s", bindingResourceName)
}

config := testAccProvider.Meta().(*Config)
p, err := config.clientBilling.BillingAccounts.GetIamPolicy("billingAccounts/" + bindingRs.Primary.Attributes["billing_account_id"]).Do()
if err != nil {
return err
}

for _, binding := range p.Bindings {
if binding.Role == role {
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", role)
}
}

func testAccCheckGoogleBillingAccountIamMemberExists(n, role, member string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["google_billing_account_iam_member."+n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

config := testAccProvider.Meta().(*Config)
p, err := config.clientBilling.BillingAccounts.GetIamPolicy("billingAccounts/" + rs.Primary.Attributes["billing_account_id"]).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)
}
}

func testAccBillingAccountIamBinding_basic(account, billingAccountId, role string) string {
return fmt.Sprintf(`
resource "google_service_account" "test-account" {
account_id = "%s"
display_name = "Iam Testing Account"
}
resource "google_billing_account_iam_binding" "foo" {
billing_account_id = "%s"
role = "%s"
members = ["serviceAccount:${google_service_account.test-account.email}"]
}
`, account, billingAccountId, role)
}

func testAccBillingAccountIamBinding_update(account, billingAccountId, role string) string {
return fmt.Sprintf(`
resource "google_service_account" "test-account" {
account_id = "%s"
display_name = "Iam Testing Account"
}
resource "google_service_account" "test-account-2" {
account_id = "%s-2"
display_name = "Iam Testing Account"
}
resource "google_billing_account_iam_binding" "foo" {
billing_account_id = "%s"
role = "%s"
members = [
"serviceAccount:${google_service_account.test-account.email}",
"serviceAccount:${google_service_account.test-account-2.email}"
]
}
`, account, account, billingAccountId, role)
}

func testAccBillingAccountIamMember_basic(account, billingAccountId, role string) string {
return fmt.Sprintf(`
resource "google_service_account" "test-account" {
account_id = "%s"
display_name = "Iam Testing Account"
}
resource "google_billing_account_iam_member" "foo" {
billing_account_id = "%s"
role = "%s"
member = "serviceAccount:${google_service_account.test-account.email}"
}
`, account, billingAccountId, role)
}
54 changes: 54 additions & 0 deletions website/docs/r/google_billing_account_iam_binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
layout: "google"
page_title: "Google: google_billing_account_iam_binding"
sidebar_current: "docs-google-billing-account-iam-binding"
description: |-
Allows management of a single binding with an IAM policy for a Google Cloud Platform Billing Account.
---

# google\_billing_account_\_iam\_binding

Allows creation and management of a single binding within IAM policy for
an existing Google Cloud Platform Billing Account.

~> **Note:** This resource __must not__ be used in conjunction with
`google_billing_account_iam_member` for the __same role__ or they will fight over
what your policy should be.

## Example Usage

```hcl
resource "google_billing_account_iam_binding" "binding" {
billing_account_id = "00AA00-000AAA-00AA0A"
role = "roles/billing.viewer"
members = [
"user:[email protected]",
]
}
```

## Argument Reference

The following arguments are supported:

* `billing_account_id` - (Required) The billing account id.

* `role` - (Required) The role that should be applied.

* `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 billing account's IAM policy.

## Import

IAM binding imports use space-delimited identifiers; first the resource in question and then the role. These bindings can be imported using the `billing_account_id` and role, e.g.

```
$ terraform import google_billing_account_iam_binding.binding "your-billing-account-id roles/viewer"
```
Loading

0 comments on commit b8895d9

Please sign in to comment.